1#[cfg(not(target_arch = "wasm32"))]
3mod ai;
4#[cfg(not(target_arch = "wasm32"))]
5mod gamepad;
6#[cfg(target_arch = "wasm32")]
7mod input_web;
8#[cfg(not(target_arch = "wasm32"))]
9pub(crate) mod jit_abi;
10
11#[cfg(not(target_arch = "wasm32"))]
14pub fn init_aot_runtime() {
15 let interp = Interpreter::new();
16 jit_abi::init(interp);
17}
18
19pub fn now_secs() -> f64 {
22 #[cfg(target_arch = "wasm32")]
23 {
24 js_sys::Date::now() / 1000.0
25 }
26 #[cfg(not(target_arch = "wasm32"))]
27 {
28 std::time::SystemTime::now()
29 .duration_since(std::time::UNIX_EPOCH)
30 .map(|d| d.as_secs_f64())
31 .unwrap_or(0.0)
32 }
33}
34
35static LINE_HUE_RATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
39static LINE_HUE_START: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
40
41pub fn set_line_hue_rate(rate: f64) {
43 LINE_HUE_RATE.store(rate.to_bits(), std::sync::atomic::Ordering::Relaxed);
44 LINE_HUE_START.store(now_secs().to_bits(), std::sync::atomic::Ordering::Relaxed);
45}
46
47pub fn line_hue_phase() -> Option<f64> {
51 let rate = f64::from_bits(LINE_HUE_RATE.load(std::sync::atomic::Ordering::Relaxed));
52 if rate <= 0.0 {
53 return None;
54 }
55 let start = f64::from_bits(LINE_HUE_START.load(std::sync::atomic::Ordering::Relaxed));
56 Some((now_secs() - start) * rate)
57}
58
59#[cfg(target_arch = "wasm32")]
62thread_local! {
63 static WASM_MODULES: std::cell::RefCell<std::collections::HashMap<String, String>> =
64 std::cell::RefCell::new(std::collections::HashMap::new());
65}
66
67#[cfg(target_arch = "wasm32")]
70pub fn register_wasm_module(path: &str, source: &str) {
71 WASM_MODULES.with(|m| m.borrow_mut().insert(path.to_string(), source.to_string()));
72}
73
74#[cfg(target_arch = "wasm32")]
76pub(crate) fn get_wasm_module(path: &str) -> Option<String> {
77 WASM_MODULES.with(|m| m.borrow().get(path).cloned())
78}
79
80#[cfg(target_arch = "wasm32")]
81#[inline]
82fn wasm_sleep_ms(ms: i32) {
83 if ms <= 0 {
84 return;
85 }
86
87 let global = js_sys::global();
90 let has_sab = js_sys::Reflect::has(
91 &global,
92 &wasm_bindgen::JsValue::from_str("SharedArrayBuffer"),
93 )
94 .unwrap_or(false);
95 let has_atomics =
96 js_sys::Reflect::has(&global, &wasm_bindgen::JsValue::from_str("Atomics")).unwrap_or(false);
97 if has_sab && has_atomics {
98 let sab = js_sys::SharedArrayBuffer::new(4);
99 let i32a = js_sys::Int32Array::new(&sab);
100 if js_sys::Atomics::wait_with_timeout(&i32a, 0, 0, ms as f64).is_ok() {
101 return;
102 }
103 }
104
105 let end = js_sys::Date::now() + ms as f64;
106 while js_sys::Date::now() < end {}
107}
108
109#[cfg(target_arch = "wasm32")]
110fn wasm_fetch_sync(
111 path: &str,
112 response_type: &str,
113 return_expr: &str,
114) -> Result<wasm_bindgen::JsValue, String> {
115 let quoted = js_sys::JSON::stringify(&wasm_bindgen::JsValue::from_str(path))
116 .ok()
117 .and_then(|s| s.as_string())
118 .unwrap_or_else(|| "\"\"".to_string());
119
120 let script = format!(
121 "(function(){{\n var xhr = new XMLHttpRequest();\n xhr.open('GET', {quoted}, false);\n xhr.responseType = '{response_type}';\n xhr.send(null);\n if ((xhr.status|0) !== 200 && (xhr.status|0) !== 0) {{ throw new Error('HTTP ' + xhr.status + ' for ' + {quoted}); }}\n return {return_expr};\n}})()"
122 );
123
124 js_sys::eval(&script).map_err(|e| {
125 e.as_string()
126 .unwrap_or_else(|| format!("JS eval failed: {:?}", e))
127 })
128}
129
130#[cfg(target_arch = "wasm32")]
131fn wasm_fetch_bytes(path: &str) -> Result<Vec<u8>, String> {
132 let value = wasm_fetch_sync(
133 path,
134 "arraybuffer",
135 "new Uint8Array(xhr.response || new ArrayBuffer(0))",
136 )?;
137 let arr = js_sys::Uint8Array::new(&value);
138 let mut out = vec![0u8; arr.length() as usize];
139 arr.copy_to(&mut out);
140 Ok(out)
141}
142
143#[cfg(target_arch = "wasm32")]
144fn wasm_fetch_text(path: &str) -> Result<String, String> {
145 let value = wasm_fetch_sync(path, "text", "String(xhr.responseText || '')")?;
146 Ok(value.as_string().unwrap_or_default())
147}
148
149#[cfg(not(target_arch = "wasm32"))]
150mod net;
151#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
152pub mod web;
153use crate::gfx::{GfxState, Light};
154use crate::parser::ast::*;
155#[cfg(target_arch = "wasm32")]
156use js_sys;
157use std::cell::RefCell;
158use std::collections::HashMap;
159use std::rc::Rc;
160use crate::gfx::raster::draw_line;
163#[cfg(not(target_arch = "wasm32"))]
164use crate::gfx::raster::fill_triangle;
165#[cfg(not(target_arch = "wasm32"))]
166use ling_audio::{AudioEngine, ToneParams, Wave};
167
168#[cfg(not(target_arch = "wasm32"))]
169use ling_audio::FftAnalyzer;
170
171#[cfg(not(target_arch = "wasm32"))]
172use ling_mic;
173
174#[derive(Debug, Clone)]
177pub enum Value {
178 Str(String),
179 Number(f64),
180 Bool(bool),
181 Unit,
182 List(Rc<Vec<Value>>),
183 Ok(Box<Value>),
184 Err(Box<Value>),
185 Fn(Vec<String>, Vec<Stmt>, Env),
186 Struct {
188 name: String,
189 fields: Vec<(String, Value)>,
190 },
191 Variant {
193 enum_name: String,
194 variant: String,
195 payload: Vec<Value>,
196 },
197}
198
199use rustc_hash::FxHashMap;
202type Env = FxHashMap<String, Value>;
203
204#[inline]
205fn new_env() -> Env {
206 FxHashMap::default()
207}
208
209impl std::fmt::Display for Value {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 match self {
212 Value::Str(s) => write!(f, "{s}"),
213 Value::Number(n) => {
214 if n.fract() == 0.0 && n.abs() < 1e15 {
215 write!(f, "{}", *n as i64)
216 } else {
217 write!(f, "{n}")
218 }
219 },
220 Value::Bool(b) => write!(f, "{b}"),
221 Value::Unit => write!(f, "()"),
222 Value::List(v) => {
223 write!(f, "[")?;
224 for (i, x) in v.iter().enumerate() {
225 if i > 0 {
226 write!(f, ", ")?;
227 }
228 write!(f, "{x}")?;
229 }
230 write!(f, "]")
231 },
232 Value::Ok(v) => write!(f, "Ok({v})"),
233 Value::Err(v) => write!(f, "Err({v})"),
234 Value::Fn(_, _, _) => write!(f, "<fn>"),
235 Value::Struct { name, fields } => {
236 write!(f, "{name} {{ ")?;
237 for (i, (k, v)) in fields.iter().enumerate() {
238 if i > 0 {
239 write!(f, ", ")?;
240 }
241 write!(f, "{k}: {v}")?;
242 }
243 write!(f, " }}")
244 },
245 Value::Variant { variant, payload, .. } => {
246 write!(f, "{variant}")?;
247 if !payload.is_empty() {
248 write!(f, "(")?;
249 for (i, v) in payload.iter().enumerate() {
250 if i > 0 {
251 write!(f, ", ")?;
252 }
253 write!(f, "{v}")?;
254 }
255 write!(f, ")")?;
256 }
257 Ok(())
258 },
259 }
260 }
261}
262
263#[cfg(target_arch = "wasm32")]
264fn wasm_unsupported_builtin(name: &str) -> Option<Value> {
265 Some(match name {
266 "audio_blip"
268 | "提示音"
269 | "ビープ音"
270 | "효과음"
271 | "เสียงบี๊บ"
272 | "ui_sound"
273 | "界面音"
274 | "UI音"
275 | "인터페이스음"
276 | "เสียงปุ่ม"
277 | "audio_stop_sfx"
278 | "停止音效"
279 | "効果音停止"
280 | "효과음정지"
281 | "หยุดเอฟเฟกต์ทั้งหมด" => Value::Unit,
282
283 "music_load"
285 | "载入音乐"
286 | "音楽読込"
287 | "음악로드"
288 | "โหลดเพลง"
289 | "music_patch"
290 | "乐器音色"
291 | "音色読込"
292 | "악기패치"
293 | "แพตช์เครื่องดนตรี"
294 | "music_lrc"
295 | "载入歌词"
296 | "歌詞読込"
297 | "가사로드"
298 | "โหลดเนื้อเพลง"
299 | "music_midi_load"
300 | "载入MIDI"
301 | "MIDI読込"
302 | "미디로드"
303 | "โหลดมิดี" => Value::Number(-1.0),
304
305 "music_duration"
306 | "音乐时长"
307 | "音楽長さ"
308 | "음악길이"
309 | "ความยาวเพลง"
310 | "music_bpm"
311 | "节拍速度"
312 | "テンポ"
313 | "템포"
314 | "จังหวะต่อนาที"
315 | "music_pos"
316 | "音乐位置"
317 | "音楽位置"
318 | "음악위치"
319 | "ตำแหน่งเพลง"
320 | "music_mic_pitch"
321 | "麦克风音高"
322 | "マイク音程"
323 | "마이크음정"
324 | "ระดับเสียงไมค์"
325 | "music_hz"
326 | "音符频率"
327 | "音符周波数"
328 | "음표주파수"
329 | "ความถี่โน้ต"
330 | "music_pitch_score"
331 | "音准评分"
332 | "音程スコア"
333 | "음정점수"
334 | "คะแนนเสียง"
335 | "music_judge"
336 | "判定"
337 | "判定する"
338 | "판정"
339 | "ตัดสินจังหวะ"
340 | "music_midi_count"
341 | "MIDI数量"
342 | "MIDI数"
343 | "미디수"
344 | "จำนวนมิดี" => Value::Number(0.0),
345
346 "music_key"
347 | "调性"
348 | "調性"
349 | "조성"
350 | "คีย์เพลง"
351 | "music_lyric"
352 | "当前歌词"
353 | "現在歌詞"
354 | "현재가사"
355 | "เนื้อเพลงปัจจุบัน"
356 | "music_note_name"
357 | "音名"
358 | "音名称"
359 | "음이름"
360 | "ชื่อโน้ต"
361 | "music_grade_name"
362 | "判定名"
363 | "判定名称"
364 | "판정이름"
365 | "ชื่อการตัดสิน" => Value::Str(String::new()),
366
367 "music_onsets"
368 | "音符起点"
369 | "オンセット"
370 | "온셋"
371 | "จุดเริ่มเสียง"
372 | "music_beat_grid"
373 | "节拍网格"
374 | "ビートグリッド"
375 | "비트그리드"
376 | "กริดจังหวะ"
377 | "music_midi_notes"
378 | "MIDI音符"
379 | "MIDIノート"
380 | "미디음표"
381 | "โน้ตมิดี"
382 | "music_midi_bars"
383 | "MIDI音条"
384 | "MIDIバー"
385 | "미디바"
386 | "แท่งมิดี"
387 | "music_fft"
388 | "音乐频谱"
389 | "音楽スペクトル"
390 | "음악스펙트럼"
391 | "สเปกตรัมเพลง" => Value::List(Vec::new().into()),
392
393 "music_play"
394 | "播放音乐"
395 | "音楽再生"
396 | "음악재생"
397 | "เล่นเพลง"
398 | "music_pause"
399 | "暂停音乐"
400 | "音楽一時停止"
401 | "음악일시정지"
402 | "หยุดเพลงชั่วคราว"
403 | "music_stop"
404 | "停止音乐"
405 | "音楽停止"
406 | "음악정지"
407 | "หยุดเพลง"
408 | "music_seek"
409 | "定位音乐"
410 | "音楽シーク"
411 | "음악탐색"
412 | "ค้นหาเพลง"
413 | "music_volume"
414 | "音乐音量"
415 | "音楽音量"
416 | "음악음량"
417 | "ระดับเพลง"
418 | "music_note"
419 | "弹音符"
420 | "音符演奏"
421 | "음표연주"
422 | "เล่นโน้ต"
423 | "music_note_on"
424 | "音符开始"
425 | "音符オン"
426 | "음표켜기"
427 | "โน้ตเริ่ม"
428 | "music_note_off"
429 | "音符结束"
430 | "音符オフ"
431 | "음표끄기"
432 | "โน้ตจบ" => Value::Unit,
433
434 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" => {
436 Value::Number(0.0)
437 },
438 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" => {
439 Value::Number(0.0)
440 },
441 "liquid_set_colors"
442 | "液体颜色"
443 | "液体配色"
444 | "액체색상"
445 | "สีของเหลว"
446 | "liquid_splat"
447 | "液体注入"
448 | "液体追加"
449 | "액체분사"
450 | "หยดของเหลว"
451 | "liquid_gravity"
452 | "液体重力"
453 | "液体重力ベクトル"
454 | "액체중력"
455 | "แรงโน้มถ่วงเหลว"
456 | "liquid_step"
457 | "液体步进"
458 | "液体更新"
459 | "액체스텝"
460 | "ก้าวของเหลว"
461 | "liquid_step_all"
462 | "液体全步进"
463 | "液体全更新"
464 | "전체액체스텝"
465 | "ก้าวของเหลวทั้งหมด"
466 | "liquid_rainbow"
467 | "液体彩虹"
468 | "液体虹"
469 | "액체무지개"
470 | "ของเหลวสายรุ้ง"
471 | "liquid_draw"
472 | "绘制液体"
473 | "液体描画"
474 | "액체그리기"
475 | "วาดของเหลว"
476 | "liquid_draw_surface"
477 | "液体贴面"
478 | "液体曲面"
479 | "액체곡면"
480 | "ของเหลวบนพื้นผิว" => Value::Unit,
481
482 "nn_new"
484 | "建神经网"
485 | "ニューラル作成"
486 | "신경망생성"
487 | "สร้างโครงข่าย"
488 | "nn_load"
489 | "载入网"
490 | "網読込"
491 | "신경망불러오기"
492 | "โหลดโครงข่าย" => Value::Number(-1.0),
493 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" => {
494 Value::List(Vec::new().into())
495 },
496 "nn_train"
497 | "训练网"
498 | "ニューラル学習"
499 | "신경망학습"
500 | "ฝึกโครงข่าย"
501 | "nn_dense"
502 | "密集层"
503 | "密層追加"
504 | "밀집층"
505 | "ชั้นหนาแน่น" => Value::Number(0.0),
506 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" => {
507 Value::Bool(false)
508 },
509
510 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" => {
512 Value::Number(-1.0)
513 },
514 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" => {
515 Value::Str(String::new())
516 },
517 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" => {
518 Value::Number(0.0)
519 },
520 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" => {
521 Value::Unit
522 },
523
524 "dialog_new"
526 | "建对话模型"
527 | "対話モデル作成"
528 | "대화모델생성"
529 | "สร้างโมเดลสนทนา"
530 | "dialog_load_model"
531 | "对话载模"
532 | "対話モデル読込"
533 | "대화모델불러오기"
534 | "โหลดโมเดลสนทนา"
535 | "dialog_train"
536 | "对话训练"
537 | "対話訓練"
538 | "대화훈련"
539 | "ฝึกสนทนา"
540 | "dialog_load"
541 | "对话载入"
542 | "対話読込"
543 | "대화불러오기"
544 | "โหลดชุดสนทนา" => Value::Number(-1.0),
545 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" => {
546 Value::Str(String::new())
547 },
548 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" => {
549 Value::Bool(false)
550 },
551 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" => {
552 Value::Unit
553 },
554
555 "net_connect"
557 | "联网"
558 | "ネット接続"
559 | "네트연결"
560 | "เชื่อมเน็ต"
561 | "net_listen"
562 | "监听"
563 | "待機"
564 | "리슨"
565 | "รอรับ"
566 | "net_send"
567 | "发送"
568 | "送信"
569 | "전송"
570 | "ส่ง" => Value::Number(-1.0),
571 "net_recv"
572 | "接收"
573 | "受信"
574 | "수신"
575 | "รับ"
576 | "net_status"
577 | "连接状态"
578 | "接続状態"
579 | "연결상태"
580 | "สถานะการเชื่อม" => Value::Str(String::new()),
581 "net_discover" | "发现" | "探索" | "검색" | "ค้นหาเครือข่าย" => {
582 Value::List(Vec::new().into())
583 },
584 "net_close"
585 | "断开"
586 | "切断"
587 | "연결종료"
588 | "ตัดเชื่อม"
589 | "net_test"
590 | "测连接"
591 | "接続テスト"
592 | "연결테스트"
593 | "ทดสอบเน็ต" => Value::Number(0.0),
594
595 _ => Value::Unit,
597 })
598}
599
600#[derive(Debug)]
603pub(crate) enum EvalErr {
604 Runtime(String),
605 Return(Value),
606 #[allow(dead_code)] Break,
608}
609
610impl From<String> for EvalErr {
611 fn from(s: String) -> Self {
612 EvalErr::Runtime(s)
613 }
614}
615
616type EvalResult = Result<Value, EvalErr>;
617
618#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
620fn base32_encode(data: &[u8]) -> String {
621 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
622 let mut out = String::new();
623 let mut buffer: u32 = 0;
624 let mut bits = 0u32;
625 for &b in data {
626 buffer = (buffer << 8) | b as u32;
627 bits += 8;
628 while bits >= 5 {
629 bits -= 5;
630 out.push(ALPHABET[((buffer >> bits) & 0x1f) as usize] as char);
631 }
632 }
633 if bits > 0 {
634 out.push(ALPHABET[((buffer << (5 - bits)) & 0x1f) as usize] as char);
635 }
636 out
637}
638
639#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
641fn base32_decode(s: &str) -> Option<Vec<u8>> {
642 let mut buffer: u32 = 0;
643 let mut bits = 0u32;
644 let mut out = Vec::new();
645 for c in s.chars() {
646 if c == '=' || c.is_whitespace() {
647 continue;
648 }
649 let v = match c.to_ascii_uppercase() {
650 'A'..='Z' => c.to_ascii_uppercase() as u32 - 'A' as u32,
651 '2'..='7' => c as u32 - '2' as u32 + 26,
652 _ => return None,
653 };
654 buffer = (buffer << 5) | v;
655 bits += 5;
656 if bits >= 8 {
657 bits -= 8;
658 out.push((buffer >> bits) as u8);
659 }
660 }
661 Some(out)
662}
663
664#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
666fn totp_code(secret_b32: &str, step: u64) -> Option<String> {
667 let key = base32_decode(secret_b32)?;
668 let msg = step.to_be_bytes();
669 let mac = hmac_sha1(&key, &msg);
670 let offset = (mac[19] & 0x0f) as usize;
671 let bin = ((mac[offset] as u32 & 0x7f) << 24)
672 | ((mac[offset + 1] as u32) << 16)
673 | ((mac[offset + 2] as u32) << 8)
674 | (mac[offset + 3] as u32);
675 Some(format!("{:06}", bin % 1_000_000))
676}
677
678#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
680fn totp_check(secret_b32: &str, code: &str) -> bool {
681 if code.len() != 6 || !code.bytes().all(|b| b.is_ascii_digit()) {
682 return false;
683 }
684 let now_step = (crate::runtime::now_secs() as u64) / 30;
685 for delta in [-1i64, 0, 1] {
686 let step = (now_step as i64 + delta) as u64;
687 if let Some(expected) = totp_code(secret_b32, step) {
688 if expected.as_bytes().ct_eq_str(code.as_bytes()) {
690 return true;
691 }
692 }
693 }
694 false
695}
696
697#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
699fn hmac_sha1(key: &[u8], msg: &[u8]) -> [u8; 20] {
700 use hmac::{Mac, SimpleHmac};
701 let mut mac = SimpleHmac::<sha1::Sha1>::new_from_slice(key).expect("hmac key");
702 mac.update(msg);
703 let out = mac.finalize().into_bytes();
704 let mut arr = [0u8; 20];
705 arr.copy_from_slice(&out);
706 arr
707}
708
709#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
711trait CtEqStr {
712 fn ct_eq_str(&self, other: &[u8]) -> bool;
713}
714#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
715impl CtEqStr for [u8] {
716 fn ct_eq_str(&self, other: &[u8]) -> bool {
717 if self.len() != other.len() {
718 return false;
719 }
720 let mut diff = 0u8;
721 for (a, b) in self.iter().zip(other.iter()) {
722 diff |= a ^ b;
723 }
724 diff == 0
725 }
726}
727
728fn url_decode(s: &str) -> String {
730 let bytes = s.as_bytes();
731 let mut out = Vec::with_capacity(bytes.len());
732 let mut i = 0;
733 while i < bytes.len() {
734 match bytes[i] {
735 b'+' => {
736 out.push(b' ');
737 i += 1;
738 },
739 b'%' if i + 2 < bytes.len() => {
740 let hi = (bytes[i + 1] as char).to_digit(16);
741 let lo = (bytes[i + 2] as char).to_digit(16);
742 if let (Some(h), Some(l)) = (hi, lo) {
743 out.push((h * 16 + l) as u8);
744 i += 3;
745 } else {
746 out.push(bytes[i]);
747 i += 1;
748 }
749 },
750 b => {
751 out.push(b);
752 i += 1;
753 },
754 }
755 }
756 String::from_utf8_lossy(&out).into_owned()
757}
758
759#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
761fn values_to_sql_params(args: &[Value]) -> Vec<ling_http::rusqlite::types::Value> {
762 use ling_http::rusqlite::types::Value as Sql;
763 args.iter()
764 .map(|v| match v {
765 Value::Number(n) if n.fract() == 0.0 && n.abs() < 9e15 => Sql::Integer(*n as i64),
766 Value::Number(n) => Sql::Real(*n),
767 Value::Bool(b) => Sql::Integer(*b as i64),
768 other => Sql::Text(other.to_string()),
769 })
770 .collect()
771}
772
773struct SvgWriter {
778 path: String,
779 width: f64,
780 height: f64,
781 elements: Vec<String>,
782}
783
784impl SvgWriter {
785 fn new(path: String, width: f64, height: f64) -> Self {
786 let bg = format!("<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>");
787 Self { path, width, height, elements: vec![bg] }
788 }
789
790 fn save(&self) -> std::io::Result<()> {
791 let w = self.width;
792 let h = self.height;
793 let mut out = format!(
794 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
795 <svg xmlns=\"http://www.w3.org/2000/svg\" \
796 width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
797 );
798 for elem in &self.elements {
799 out.push_str(" ");
800 out.push_str(elem);
801 out.push('\n');
802 }
803 out.push_str("</svg>\n");
804 if let Some(parent) = std::path::Path::new(&self.path).parent() {
806 if !parent.as_os_str().is_empty() {
807 let _ = std::fs::create_dir_all(parent);
808 }
809 }
810 std::fs::write(&self.path, out.as_bytes())
811 }
812}
813
814fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
815 let s = s / 100.0;
816 let l = l / 100.0;
817 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
818 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
819 let m = l - c / 2.0;
820 let (r1, g1, b1) = if h < 60.0 {
821 (c, x, 0.0)
822 } else if h < 120.0 {
823 (x, c, 0.0)
824 } else if h < 180.0 {
825 (0.0, c, x)
826 } else if h < 240.0 {
827 (0.0, x, c)
828 } else if h < 300.0 {
829 (x, 0.0, c)
830 } else {
831 (c, 0.0, x)
832 };
833 let r = ((r1 + m) * 255.0).round() as u8;
834 let g = ((g1 + m) * 255.0).round() as u8;
835 let b = ((b1 + m) * 255.0).round() as u8;
836 format!("#{r:02x}{g:02x}{b:02x}")
837}
838
839fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
842 let mut h = (x as u32)
843 .wrapping_add((y as u32).wrapping_mul(2654435769))
844 .wrapping_add(seed.wrapping_mul(1234567891));
845 h ^= h >> 16;
846 h = h.wrapping_mul(0x45d9f3b);
847 h ^= h >> 16;
848 h as f32 / u32::MAX as f32
849}
850
851fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
852 let xi = x.floor() as i32;
853 let yi = y.floor() as i32;
854 let sm = |t: f32| t * t * (3.0 - 2.0 * t);
855 let xf = sm(x - xi as f32);
856 let yf = sm(y - yi as f32);
857 let a = tex_hash(xi, yi, seed);
858 let b = tex_hash(xi + 1, yi, seed);
859 let c = tex_hash(xi, yi + 1, seed);
860 let d = tex_hash(xi + 1, yi + 1, seed);
861 a + (b - a) * xf + (c - a) * yf + (a - b - c + d) * xf * yf
862}
863
864fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
865 let mut v = 0.0f32;
866 let mut amp = 0.5f32;
867 let mut f = 1.0f32;
868 for i in 0..octaves {
869 v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
870 amp *= 0.5;
871 f *= 2.0;
872 }
873 v
874}
875
876fn tex_palette(name: &str, t: f32) -> [f32; 3] {
877 let (a, b, c, d): ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) = match name {
878 "fire" => (
879 [0.8, 0.4, 0.1],
880 [0.7, 0.3, 0.1],
881 [1.0, 0.5, 0.3],
882 [0.0, 0.5, 0.8],
883 ),
884 "ocean" => (
885 [0.1, 0.4, 0.7],
886 [0.3, 0.3, 0.4],
887 [0.8, 1.0, 0.5],
888 [0.3, 0.0, 0.6],
889 ),
890 "psychedelic" => (
891 [0.5, 0.5, 0.5],
892 [0.8, 0.8, 0.8],
893 [1.0, 1.3, 0.7],
894 [0.0, 0.15, 0.3],
895 ),
896 "neon" => (
897 [0.5, 0.5, 0.5],
898 [0.5, 0.5, 0.5],
899 [2.0, 1.0, 0.0],
900 [0.5, 0.2, 0.25],
901 ),
902 "forest" => (
903 [0.3, 0.5, 0.2],
904 [0.2, 0.3, 0.1],
905 [1.0, 0.5, 0.8],
906 [0.1, 0.3, 0.6],
907 ),
908 _ => (
909 [0.5, 0.5, 0.5],
910 [0.5, 0.5, 0.5],
911 [1.0, 1.0, 1.0],
912 [0.0, 0.333, 0.667],
913 ),
914 };
915 [0, 1, 2]
916 .map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
917}
918
919#[cfg(not(target_arch = "wasm32"))]
921fn key_char(k: minifb::Key, shift: bool) -> Option<char> {
927 use minifb::Key::*;
928 let base = match k {
929 A => 'a',
930 B => 'b',
931 C => 'c',
932 D => 'd',
933 E => 'e',
934 F => 'f',
935 G => 'g',
936 H => 'h',
937 I => 'i',
938 J => 'j',
939 K => 'k',
940 L => 'l',
941 M => 'm',
942 N => 'n',
943 O => 'o',
944 P => 'p',
945 Q => 'q',
946 R => 'r',
947 S => 's',
948 T => 't',
949 U => 'u',
950 V => 'v',
951 W => 'w',
952 X => 'x',
953 Y => 'y',
954 Z => 'z',
955 Key0 => '0',
956 Key1 => '1',
957 Key2 => '2',
958 Key3 => '3',
959 Key4 => '4',
960 Key5 => '5',
961 Key6 => '6',
962 Key7 => '7',
963 Key8 => '8',
964 Key9 => '9',
965 Space => ' ',
966 Minus => '-',
967 Equal => '=',
968 Period => '.',
969 Comma => ',',
970 Slash => '/',
971 Backslash => '\\',
972 Semicolon => ';',
973 Apostrophe => '\'',
974 LeftBracket => '[',
975 RightBracket => ']',
976 Backquote => '`',
977 _ => return None,
978 };
979 if !shift {
980 return Some(base);
981 }
982 Some(match base {
983 'a'..='z' => base.to_ascii_uppercase(),
984 '0' => ')',
985 '1' => '!',
986 '2' => '@',
987 '3' => '#',
988 '4' => '$',
989 '5' => '%',
990 '6' => '^',
991 '7' => '&',
992 '8' => '*',
993 '9' => '(',
994 '-' => '_',
995 '=' => '+',
996 '.' => '>',
997 ',' => '<',
998 '/' => '?',
999 '\\' => '|',
1000 ';' => ':',
1001 '\'' => '"',
1002 '[' => '{',
1003 ']' => '}',
1004 '`' => '~',
1005 other => other,
1006 })
1007}
1008
1009#[cfg(all(not(target_arch = "wasm32"), windows))]
1013fn vk_char(vk: i32, shift: bool) -> Option<char> {
1014 let base = match vk {
1015 0x41..=0x5A => (b'a' + (vk - 0x41) as u8) as char, 0x30..=0x39 => (b'0' + (vk - 0x30) as u8) as char, 0x20 => ' ', 0xBD => '-', 0xBB => '=', 0xBE => '.', 0xBC => ',', 0xBF => '/', 0xDC => '\\', 0xBA => ';', 0xDE => '\'', 0xDB => '[', 0xDD => ']', 0xC0 => '`', _ => return None,
1030 };
1031 if !shift {
1032 return Some(base);
1033 }
1034 Some(match base {
1035 'a'..='z' => base.to_ascii_uppercase(),
1036 '0' => ')',
1037 '1' => '!',
1038 '2' => '@',
1039 '3' => '#',
1040 '4' => '$',
1041 '5' => '%',
1042 '6' => '^',
1043 '7' => '&',
1044 '8' => '*',
1045 '9' => '(',
1046 '-' => '_',
1047 '=' => '+',
1048 '.' => '>',
1049 ',' => '<',
1050 '/' => '?',
1051 '\\' => '|',
1052 ';' => ':',
1053 '\'' => '"',
1054 '[' => '{',
1055 ']' => '}',
1056 '`' => '~',
1057 other => other,
1058 })
1059}
1060
1061#[cfg(all(not(target_arch = "wasm32"), windows))]
1064const TEXT_POLL_VKS: &[i32] = &[
1065 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
1066 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x30, 0x31, 0x32, 0x33,
1067 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0xBD, 0xBB, 0xBE, 0xBC, 0xBF, 0xDC, 0xBA, 0xDE,
1068 0xDB, 0xDD, 0xC0,
1069];
1070#[cfg(all(not(target_arch = "wasm32"), windows))]
1073const VK_BACK: i32 = 0x08;
1074#[cfg(all(not(target_arch = "wasm32"), windows))]
1075const VK_SHIFT: i32 = 0x10;
1076
1077#[cfg(all(not(target_arch = "wasm32"), windows))]
1084fn os_key_down(vk: i32) -> bool {
1085 unsafe {
1086 extern "system" {
1087 fn GetAsyncKeyState(vkey: i32) -> i16;
1088 }
1089 (GetAsyncKeyState(vk) as u16 & 0x8000) != 0
1090 }
1091}
1092
1093#[cfg(all(not(target_arch = "wasm32"), windows))]
1101fn window_is_foreground(hwnd: isize) -> bool {
1102 unsafe {
1103 extern "system" {
1104 fn GetForegroundWindow() -> isize;
1105 }
1106 hwnd != 0 && GetForegroundWindow() == hwnd
1107 }
1108}
1109
1110#[cfg(all(not(target_arch = "wasm32"), windows))]
1113const KEY_REPEAT_DELAY: f64 = 0.45;
1114#[cfg(all(not(target_arch = "wasm32"), windows))]
1116const KEY_REPEAT_RATE: f64 = 0.045;
1117
1118#[cfg(all(not(target_arch = "wasm32"), windows))]
1125fn key_repeat_fire(now: f64, down: bool, was_down: bool, down_since: &mut f64, last_fire: &mut f64) -> bool {
1126 if !down {
1127 return false;
1128 }
1129 if !was_down {
1130 *down_since = now;
1131 *last_fire = now;
1132 return true;
1133 }
1134 if now - *down_since >= KEY_REPEAT_DELAY && now - *last_fire >= KEY_REPEAT_RATE {
1135 *last_fire = now;
1136 return true;
1137 }
1138 false
1139}
1140
1141#[cfg(all(not(target_arch = "wasm32"), windows))]
1145fn str_to_vk(name: &str) -> Option<i32> {
1146 Some(match name {
1147 "numpad0" | "kp0" => 0x60,
1148 "numpad1" | "kp1" => 0x61,
1149 "numpad2" | "kp2" => 0x62,
1150 "numpad3" | "kp3" => 0x63,
1151 "numpad4" | "kp4" => 0x64,
1152 "numpad5" | "kp5" => 0x65,
1153 "numpad6" | "kp6" => 0x66,
1154 "numpad7" | "kp7" => 0x67,
1155 "numpad8" | "kp8" => 0x68,
1156 "numpad9" | "kp9" => 0x69,
1157 "numpad+" | "kp+" => 0x6B,
1158 "numpad-" | "kp-" => 0x6D,
1159 "numpad*" | "kp*" => 0x6A,
1160 "numpad/" | "kp/" => 0x6F,
1161 "left" => 0x25,
1162 "up" => 0x26,
1163 "right" => 0x27,
1164 "down" => 0x28,
1165 "space" => 0x20,
1166 "enter" => 0x0D,
1167 "escape" => 0x1B,
1168 "pageup" => 0x21,
1169 "pagedown" => 0x22,
1170 "lshift" | "leftshift" => 0xA0,
1171 "rshift" | "rightshift" => 0xA1,
1172 "lctrl" | "leftctrl" => 0xA2,
1173 "rctrl" | "rightctrl" => 0xA3,
1174 "lalt" | "leftalt" => 0xA4,
1175 "ralt" | "rightalt" => 0xA5,
1176 "tab" => 0x09,
1177 "backspace" => VK_BACK,
1178 "delete" => 0x2E,
1179 "insert" => 0x2D,
1180 "home" => 0x24,
1181 "end" => 0x23,
1182 "a" => 0x41,
1183 "b" => 0x42,
1184 "c" => 0x43,
1185 "d" => 0x44,
1186 "e" => 0x45,
1187 "f" => 0x46,
1188 "g" => 0x47,
1189 "h" => 0x48,
1190 "i" => 0x49,
1191 "j" => 0x4A,
1192 "k" => 0x4B,
1193 "l" => 0x4C,
1194 "m" => 0x4D,
1195 "n" => 0x4E,
1196 "o" => 0x4F,
1197 "p" => 0x50,
1198 "q" => 0x51,
1199 "r" => 0x52,
1200 "s" => 0x53,
1201 "t" => 0x54,
1202 "u" => 0x55,
1203 "v" => 0x56,
1204 "w" => 0x57,
1205 "x" => 0x58,
1206 "y" => 0x59,
1207 "z" => 0x5A,
1208 "0" => 0x30,
1209 "1" => 0x31,
1210 "2" => 0x32,
1211 "3" => 0x33,
1212 "4" => 0x34,
1213 "5" => 0x35,
1214 "6" => 0x36,
1215 "7" => 0x37,
1216 "8" => 0x38,
1217 "9" => 0x39,
1218 _ => return None,
1219 })
1220}
1221
1222fn hex_encode(bytes: &[u8]) -> String {
1224 let mut s = String::with_capacity(bytes.len() * 2);
1225 for b in bytes {
1226 s.push_str(&format!("{b:02x}"));
1227 }
1228 s
1229}
1230
1231#[cfg(not(target_arch = "wasm32"))]
1233fn decode_blob(s: &str) -> Result<Vec<u8>, String> {
1234 use base64::Engine as _;
1235 use std::io::Read as _;
1236 let comp = base64::engine::general_purpose::STANDARD
1237 .decode(s.trim())
1238 .map_err(|e| format!("base64: {e}"))?;
1239 let mut out = Vec::new();
1240 flate2::read::ZlibDecoder::new(&comp[..])
1241 .read_to_end(&mut out)
1242 .map_err(|e| format!("inflate: {e}"))?;
1243 Ok(out)
1244}
1245
1246#[cfg(not(target_arch = "wasm32"))]
1248fn hex_decode(s: &str) -> Vec<u8> {
1249 let s = s.trim();
1250 (0..s.len() / 2)
1251 .filter_map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
1252 .collect()
1253}
1254
1255#[cfg(not(target_arch = "wasm32"))]
1257fn hex_to_32(s: &str) -> [u8; 32] {
1258 let v = hex_decode(s);
1259 let mut out = [0u8; 32];
1260 let n = v.len().min(32);
1261 out[..n].copy_from_slice(&v[..n]);
1262 out
1263}
1264
1265#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1273fn build_pdf_from_images(paths: &[String], out_path: &str) -> Result<(), Box<dyn std::error::Error>> {
1274 use std::io::Write as _;
1275
1276 struct PageImg {
1277 w: u32,
1278 h: u32,
1279 compressed: Vec<u8>,
1280 }
1281
1282 let mut pages = Vec::with_capacity(paths.len());
1283 for p in paths {
1284 let img = image::open(p)?.to_rgb8();
1285 let (w, h) = (img.width(), img.height());
1286 let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
1287 enc.write_all(img.as_raw())?;
1288 pages.push(PageImg { w, h, compressed: enc.finish()? });
1289 }
1290
1291 let n = pages.len();
1292 let page_nums: Vec<u32> = (0..n).map(|i| 3 + (i as u32) * 3).collect();
1295 let total_objs = 2 + n * 3;
1296 let mut off = vec![0usize; total_objs + 1]; let mut buf: Vec<u8> = Vec::new();
1299 buf.extend_from_slice(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
1300
1301 off[1] = buf.len();
1302 buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1303
1304 off[2] = buf.len();
1305 let kids = page_nums.iter().map(|n| format!("{n} 0 R")).collect::<Vec<_>>().join(" ");
1306 buf.extend_from_slice(format!("2 0 obj\n<< /Type /Pages /Kids [{kids}] /Count {n} >>\nendobj\n").as_bytes());
1307
1308 for (i, page) in pages.iter().enumerate() {
1309 let page_num = page_nums[i];
1310 let content_num = page_num + 1;
1311 let image_num = page_num + 2;
1312 let (w, h) = (page.w, page.h);
1313
1314 off[page_num as usize] = buf.len();
1315 buf.extend_from_slice(format!(
1316 "{page_num} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {w} {h}] /Resources << /XObject << /Im0 {image_num} 0 R >> >> /Contents {content_num} 0 R >>\nendobj\n"
1317 ).as_bytes());
1318
1319 let content = format!("q {w} 0 0 {h} 0 0 cm /Im0 Do Q");
1320 off[content_num as usize] = buf.len();
1321 buf.extend_from_slice(format!(
1322 "{content_num} 0 obj\n<< /Length {} >>\nstream\n{content}\nendstream\nendobj\n",
1323 content.len()
1324 ).as_bytes());
1325
1326 off[image_num as usize] = buf.len();
1327 buf.extend_from_slice(format!(
1328 "{image_num} 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>\nstream\n",
1329 page.compressed.len()
1330 ).as_bytes());
1331 buf.extend_from_slice(&page.compressed);
1332 buf.extend_from_slice(b"\nendstream\nendobj\n");
1333 }
1334
1335 let xref_offset = buf.len();
1336 buf.extend_from_slice(format!("xref\n0 {}\n", total_objs + 1).as_bytes());
1337 buf.extend_from_slice(b"0000000000 65535 f \n");
1338 for entry in off.iter().skip(1) {
1339 buf.extend_from_slice(format!("{entry:010} 00000 n \n").as_bytes());
1340 }
1341 buf.extend_from_slice(
1342 format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF", total_objs + 1).as_bytes(),
1343 );
1344
1345 std::fs::write(out_path, buf)?;
1346 Ok(())
1347}
1348
1349fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
1350 ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
1351}
1352
1353const PERM: [u8; 512] = [
1356 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1357 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1358 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1359 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1360 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1361 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1362 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1363 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1364 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1365 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1366 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180,
1367 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
1368 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
1369 140, 161, 137, 13, 191, 230, 66, 104, 153, 199, 167, 147, 99, 179, 92,
1370 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1372 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1373 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1374 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1375 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1376 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1377 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1378 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1379 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1380 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1381 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174,
1382];
1383
1384fn fade(t: f32) -> f32 {
1385 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1386}
1387
1388fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
1389 let h = hash & 15;
1390 let u = if h < 8 { x } else { y };
1391 let v = if h < 8 { y } else { z };
1392 (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v })
1393}
1394
1395fn perlin3(x: f32, y: f32, z: f32) -> f32 {
1396 let xi = (x.floor() as i32) & 255;
1397 let yi = (y.floor() as i32) & 255;
1398 let zi = (z.floor() as i32) & 255;
1399
1400 let xf = x - x.floor();
1401 let yf = y - y.floor();
1402 let zf = z - z.floor();
1403
1404 let u = fade(xf);
1405 let v = fade(yf);
1406 let w = fade(zf);
1407
1408 let p0 = PERM[xi as usize] as usize;
1409 let p1 = PERM[((xi + 1) & 255) as usize] as usize;
1410 let pa = PERM[(p0 + yi as usize) & 255] as usize;
1411 let pb = PERM[(p0 + ((yi + 1) & 255) as usize) & 255] as usize;
1412 let pc = PERM[(p1 + yi as usize) & 255] as usize;
1413 let pd = PERM[(p1 + ((yi + 1) & 255) as usize) & 255] as usize;
1414
1415 let g000 = grad(PERM[(pa + zi as usize) & 255], xf, yf, zf);
1416 let g001 = grad(
1417 PERM[(pa + ((zi + 1) & 255) as usize) & 255],
1418 xf,
1419 yf,
1420 zf - 1.0,
1421 );
1422 let g010 = grad(PERM[(pb + zi as usize) & 255], xf, yf - 1.0, zf);
1423 let g011 = grad(
1424 PERM[(pb + ((zi + 1) & 255) as usize) & 255],
1425 xf,
1426 yf - 1.0,
1427 zf - 1.0,
1428 );
1429 let g100 = grad(PERM[(pc + zi as usize) & 255], xf - 1.0, yf, zf);
1430 let g101 = grad(
1431 PERM[(pc + ((zi + 1) & 255) as usize) & 255],
1432 xf - 1.0,
1433 yf,
1434 zf - 1.0,
1435 );
1436 let g110 = grad(PERM[(pd + zi as usize) & 255], xf - 1.0, yf - 1.0, zf);
1437 let g111 = grad(
1438 PERM[(pd + ((zi + 1) & 255) as usize) & 255],
1439 xf - 1.0,
1440 yf - 1.0,
1441 zf - 1.0,
1442 );
1443
1444 let l00 = g000 + u * (g100 - g000);
1445 let l01 = g001 + u * (g101 - g001);
1446 let l10 = g010 + u * (g110 - g010);
1447 let l11 = g011 + u * (g111 - g011);
1448
1449 let l0 = l00 + v * (l10 - l00);
1450 let l1 = l01 + v * (l11 - l01);
1451
1452 l0 + w * (l1 - l0)
1453}
1454
1455fn fast_rand_f64(state: &mut u64) -> f64 {
1456 *state = state
1457 .wrapping_mul(6364136223846793005)
1458 .wrapping_add(1442695040888963407);
1459 ((*state >> 32) as u32) as f64 / 4294967296.0
1460}
1461
1462#[inline]
1466fn put_px(buf: &mut [u32], idx: usize, color: u32, blend: u8) {
1467 if idx >= buf.len() {
1468 return;
1469 }
1470 if blend == 0 {
1471 buf[idx] = color;
1472 } else {
1473 let old = buf[idx];
1474 let r = (((old >> 16) & 255) + ((color >> 16) & 255)).min(255);
1475 let g = (((old >> 8) & 255) + ((color >> 8) & 255)).min(255);
1476 let b = ((old & 255) + (color & 255)).min(255);
1477 buf[idx] = (r << 16) | (g << 8) | b;
1478 }
1479}
1480
1481#[inline]
1483fn rgb(r: f64, g: f64, b: f64) -> u32 {
1484 let r = (r as i64).clamp(0, 255) as u32;
1485 let g = (g as i64).clamp(0, 255) as u32;
1486 let b = (b as i64).clamp(0, 255) as u32;
1487 (r << 16) | (g << 8) | b
1488}
1489
1490#[allow(clippy::too_many_arguments)]
1491fn draw_circle_outline(
1492 buf: &mut [u32],
1493 w: i32,
1494 h: i32,
1495 cx: i32,
1496 cy: i32,
1497 r: i32,
1498 color: u32,
1499 blend: u8,
1500) {
1501 let r = r.clamp(0, 20000); if r == 0 {
1503 return;
1504 }
1505 let mut x = 0;
1506 let mut y = r;
1507 let mut d = 3 - 2 * r;
1508 while x <= y {
1509 plot_circle_points(buf, w, h, cx, cy, x, y, color, blend);
1510 if d < 0 {
1511 d += 4 * x + 6;
1512 } else {
1513 d += 4 * (x - y) + 10;
1514 y -= 1;
1515 }
1516 x += 1;
1517 }
1518}
1519
1520#[allow(clippy::too_many_arguments)]
1521fn plot_circle_points(
1522 buf: &mut [u32],
1523 w: i32,
1524 h: i32,
1525 cx: i32,
1526 cy: i32,
1527 x: i32,
1528 y: i32,
1529 color: u32,
1530 blend: u8,
1531) {
1532 let points = [
1533 (cx + x, cy + y),
1534 (cx - x, cy + y),
1535 (cx + x, cy - y),
1536 (cx - x, cy - y),
1537 (cx + y, cy + x),
1538 (cx - y, cy + x),
1539 (cx + y, cy - x),
1540 (cx - y, cy - x),
1541 ];
1542 for &(px, py) in &points {
1543 if px >= 0 && px < w && py >= 0 && py < h {
1544 put_px(buf, (py * w + px) as usize, color, blend);
1545 }
1546 }
1547}
1548
1549#[allow(clippy::too_many_arguments)]
1550fn draw_circle_filled(
1551 buf: &mut [u32],
1552 w: i32,
1553 h: i32,
1554 cx: i32,
1555 cy: i32,
1556 r: i32,
1557 color: u32,
1558 blend: u8,
1559) {
1560 if r <= 0 {
1561 return;
1562 }
1563 for dy in -r..=r {
1564 let dx_max = ((r * r - dy * dy) as f64).sqrt() as i32;
1565 let py = cy + dy;
1566 if py < 0 || py >= h {
1567 continue;
1568 }
1569 for dx in -dx_max..=dx_max {
1570 let px = cx + dx;
1571 if px >= 0 && px < w {
1572 put_px(buf, (py * w + px) as usize, color, blend);
1573 }
1574 }
1575 }
1576}
1577
1578#[cfg(test)]
1579mod draw_tests {
1580 use super::*;
1581
1582 #[test]
1583 fn filled_circle_actually_writes_pixels() {
1584 let mut buf = vec![0u32; 100 * 100];
1585 draw_circle_filled(&mut buf, 100, 100, 50, 50, 10, 0xFF00FF, 0);
1586 assert_eq!(buf[50 * 100 + 50], 0xFF00FF, "centre pixel must be filled");
1587 assert_eq!(buf[0], 0, "far corner must stay clear");
1588 let n = buf.iter().filter(|&&p| p != 0).count();
1589 assert!(n > 200 && n < 500, "r=10 disc area ≈ 314, got {n}");
1590 }
1591
1592 #[test]
1593 fn circle_outline_writes_a_ring() {
1594 let mut buf = vec![0u32; 100 * 100];
1595 draw_circle_outline(&mut buf, 100, 100, 50, 50, 20, 0x00FF00, 0);
1596 assert_eq!(buf[50 * 100 + 50], 0, "outline must NOT fill the centre");
1597 assert!(
1598 buf.iter().any(|&p| p == 0x00FF00),
1599 "outline must draw a ring"
1600 );
1601 }
1602
1603 #[test]
1604 fn additive_blend_accumulates_channels() {
1605 let mut buf = vec![0x202020u32; 1];
1606 put_px(&mut buf, 0, 0x404040, 1);
1607 assert_eq!(buf[0], 0x606060);
1608 }
1609}
1610
1611#[derive(Clone, Copy)]
1617pub struct UiTheme {
1618 pub primary: u32,
1619 pub accent: u32,
1620 pub track: u32,
1621 pub warn: u32,
1622 pub text: u32,
1623 pub bg: u32,
1624}
1625
1626impl Default for UiTheme {
1627 fn default() -> Self {
1628 Self {
1629 primary: 0x00D2FF, accent: 0x28FFB4, track: 0x2C3E64, warn: 0xFF5A5A, text: 0xBEEBFF, bg: 0x0A1018, }
1636 }
1637}
1638
1639#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
1640pub struct Interpreter {
1641 globals: HashMap<String, Expr>,
1642 global_seed: Env,
1645 functions: FxHashMap<String, Rc<FnDef>>,
1646 pub(crate) structs: HashMap<String, Vec<String>>,
1648 enum_variants: HashMap<String, (String, usize)>,
1650 _modules: HashMap<String, Vec<FnDef>>,
1651 gfx: RefCell<GfxState>,
1652 svg: RefCell<Option<SvgWriter>>,
1653 pub source_dir: Option<std::path::PathBuf>,
1655 loaded_files: std::collections::HashSet<String>,
1657 #[cfg(not(target_arch = "wasm32"))]
1659 audio: Option<AudioEngine>,
1660 #[cfg(not(target_arch = "wasm32"))]
1661 fft: RefCell<FftAnalyzer>,
1662 fft_bands_cache: RefCell<Vec<f32>>,
1663 start_time_secs: f64,
1666 frame_num: u64,
1668 #[cfg(target_arch = "wasm32")]
1670 wasm_target_fps: f64,
1671 #[cfg(target_arch = "wasm32")]
1673 wasm_next_present_ms: f64,
1674 rand_state: u64,
1676 #[cfg(not(target_arch = "wasm32"))]
1678 mic: Option<ling_mic::MicInput>,
1679 #[cfg(not(target_arch = "wasm32"))]
1681 crypto_ids: Vec<ling_crypto::KnotIdentity>,
1682 #[cfg(not(target_arch = "wasm32"))]
1684 ed25519_ids: Vec<ling_crypto::Ed25519Keypair>,
1685 text_buffer: String,
1687 record_n: u32,
1689 #[cfg(not(target_arch = "wasm32"))]
1691 mic_buffer: Vec<f32>,
1692 #[cfg(not(target_arch = "wasm32"))]
1694 fonts: Vec<ling_graphics::VectorFont>,
1695 images: Vec<image::RgbaImage>,
1698 ui_theme: UiTheme,
1700 mouse_was_down: bool,
1702 #[cfg(not(target_arch = "wasm32"))]
1704 music: Option<ling_music::MusicEngine>,
1705 #[cfg(not(target_arch = "wasm32"))]
1706 music_init: bool,
1707 tracks: Vec<ling_music::DecodedAudio>,
1709 lyrics: Vec<ling_music::Lyrics>,
1711 midis: Vec<ling_music::MidiSong>,
1713 soft_bodies: Vec<ling_physics::soft::SoftBody>,
1715 rigid_world: ling_physics::rigid::PhysicsWorld,
1717 liquids: Vec<ling_physics::liquid::LiquidGrid>,
1719 meshes: Vec<crate::gfx::shapes::ColorMesh>,
1720 gltf_models: std::cell::RefCell<Vec<ling_physics::gltf::GltfModel>>,
1722 dialog: Option<ling_game::dialog::Dialog>,
1724 dialog_colors: [u32; 4],
1726 frames: Vec<String>,
1728 error_trace: Option<Vec<String>>,
1731 #[cfg(not(target_arch = "wasm32"))]
1735 input: RefCell<Option<InputState>>,
1736 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1741 http_routes: Vec<(String, String, Value)>,
1742 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1746 db: Option<ling_http::rusqlite::Connection>,
1747 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1750 http_static_dirs: Vec<(String, String)>,
1751 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1755 async_jobs: web::AsyncJobs,
1756}
1757
1758#[cfg(not(target_arch = "wasm32"))]
1760struct InputState {
1761 sensorium: ling_input::Sensorium,
1762 backend: ling_input::backend::GilrsBackend,
1763}
1764
1765impl Default for Interpreter {
1766 fn default() -> Self {
1767 Self::new()
1768 }
1769}
1770
1771impl Interpreter {
1772 pub fn new() -> Self {
1773 #[cfg(not(target_arch = "wasm32"))]
1774 let audio = AudioEngine::new()
1775 .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
1776 .ok();
1777 Self {
1778 globals: HashMap::new(),
1779 global_seed: new_env(),
1780 functions: FxHashMap::default(),
1781 structs: HashMap::new(),
1782 enum_variants: HashMap::new(),
1783 _modules: HashMap::new(),
1784 gfx: RefCell::new(GfxState::new()),
1785 svg: RefCell::new(None),
1786 source_dir: None,
1787 loaded_files: std::collections::HashSet::new(),
1788 #[cfg(not(target_arch = "wasm32"))]
1789 audio,
1790 #[cfg(not(target_arch = "wasm32"))]
1791 fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
1792 fft_bands_cache: RefCell::new(vec![]),
1793 start_time_secs: crate::runtime::now_secs(),
1794 frame_num: 0,
1795 #[cfg(target_arch = "wasm32")]
1796 wasm_target_fps: 60.0,
1797 #[cfg(target_arch = "wasm32")]
1798 wasm_next_present_ms: 0.0,
1799 rand_state: 0x123456789ABCDEF,
1800 #[cfg(not(target_arch = "wasm32"))]
1801 mic: None,
1802 #[cfg(not(target_arch = "wasm32"))]
1803 crypto_ids: Vec::new(),
1804 #[cfg(not(target_arch = "wasm32"))]
1805 ed25519_ids: Vec::new(),
1806 text_buffer: String::new(),
1807 record_n: 0,
1808 #[cfg(not(target_arch = "wasm32"))]
1809 mic_buffer: Vec::new(),
1810 #[cfg(not(target_arch = "wasm32"))]
1811 fonts: Vec::new(),
1812 images: Vec::new(),
1813 ui_theme: UiTheme::default(),
1814 mouse_was_down: false,
1815 #[cfg(not(target_arch = "wasm32"))]
1816 music: None,
1817 #[cfg(not(target_arch = "wasm32"))]
1818 music_init: false,
1819 tracks: Vec::new(),
1820 lyrics: Vec::new(),
1821 midis: Vec::new(),
1822 soft_bodies: Vec::new(),
1823 rigid_world: ling_physics::rigid::PhysicsWorld::new(),
1824 liquids: Vec::new(),
1825 meshes: Vec::new(),
1826 gltf_models: std::cell::RefCell::new(Vec::new()),
1827 dialog: None,
1828 dialog_colors: [0xE6F2FF, 0xFFD24A, 0x4AD2FF, 0x6CFF8C], frames: Vec::new(),
1830 error_trace: None,
1831 #[cfg(not(target_arch = "wasm32"))]
1832 input: RefCell::new(None),
1833 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1834 http_routes: Vec::new(),
1835 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1836 db: None,
1837 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1838 http_static_dirs: Vec::new(),
1839 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1840 async_jobs: web::AsyncJobs::new(),
1841 }
1842 }
1843
1844 #[cfg(not(target_arch = "wasm32"))]
1848 fn pad_poll(&self) -> usize {
1849 let mut slot = self.input.borrow_mut();
1850 if slot.is_none() {
1851 match ling_input::backend::GilrsBackend::new() {
1852 Ok(backend) => {
1853 *slot = Some(InputState { sensorium: ling_input::Sensorium::new(4), backend });
1854 },
1855 Err(_) => return 0,
1856 }
1857 }
1858 let st = slot.as_mut().unwrap();
1859 st.sensorium.begin_frame();
1860 st.sensorium.pump(&mut st.backend);
1861 st.sensorium.update(1.0 / 60.0);
1862 st.sensorium.devices.count()
1863 }
1864
1865 #[cfg(not(target_arch = "wasm32"))]
1868 fn with_pad<T>(&self, slot: usize, default: T, f: impl FnOnce(&ling_input::Gamepad) -> T) -> T {
1869 let inp = self.input.borrow();
1870 match inp.as_ref().and_then(|s| s.sensorium.player(slot)) {
1871 Some(p) => f(p),
1872 None => default,
1873 }
1874 }
1875
1876 pub fn take_error_trace(&mut self) -> Vec<String> {
1879 self.error_trace.take().unwrap_or_default()
1880 }
1881
1882 #[cfg(target_arch = "wasm32")]
1883 fn wasm_pace_frame(&mut self) {
1884 let fps = self.wasm_target_fps.max(1.0);
1885 let frame_ms = 1000.0 / fps;
1886 let now = js_sys::Date::now();
1887 if self.wasm_next_present_ms <= 0.0 {
1888 self.wasm_next_present_ms = now + frame_ms;
1889 return;
1890 }
1891
1892 let wait_ms = (self.wasm_next_present_ms - now).floor() as i32;
1893 if wait_ms > 0 {
1894 wasm_sleep_ms(wait_ms);
1895 }
1896
1897 let after = js_sys::Date::now();
1898 if after > self.wasm_next_present_ms + frame_ms * 3.0 {
1899 self.wasm_next_present_ms = after + frame_ms;
1900 } else {
1901 self.wasm_next_present_ms += frame_ms;
1902 }
1903 }
1904
1905 fn framed<T, F>(&mut self, name: &str, body: F) -> Result<T, EvalErr>
1908 where
1909 F: FnOnce(&mut Self) -> Result<T, EvalErr>,
1910 {
1911 self.frames.push(name.to_string());
1912 let result = body(self);
1913 if matches!(result, Err(EvalErr::Runtime(_))) && self.error_trace.is_none() {
1914 self.error_trace = Some(self.frames.clone());
1915 }
1916 self.frames.pop();
1917 result
1918 }
1919
1920 #[cfg(not(target_arch = "wasm32"))]
1924 fn render_dialog(&mut self, x: f32, y: f32, w: f32, h: f32, font: i64, t: f32) {
1925 let (runs, typing) = match &self.dialog {
1926 Some(d) if !d.is_closed() => {
1927 let runs: Vec<(String, usize, bool)> = d
1928 .visible_runs()
1929 .into_iter()
1930 .map(|r| (r.text, r.role.index(), r.newline_before))
1931 .collect();
1932 (runs, d.is_typing())
1933 },
1934 _ => return,
1935 };
1936 let colors = self.dialog_colors;
1937 let b = 12.0;
1939 let corners: Vec<[f32; 2]> = vec![
1940 [x + b, y],
1941 [x + w - b, y],
1942 [x + w, y + b],
1943 [x + w, y + h - b],
1944 [x + w - b, y + h],
1945 [x + b, y + h],
1946 [x, y + h - b],
1947 [x, y + b],
1948 [x + b, y],
1949 ];
1950 {
1951 let mut gfx = self.gfx.borrow_mut();
1952 let (bw, bh) = (gfx.width, gfx.height);
1953 crate::gfx::raster::fill_contours_aa(
1954 &mut gfx.buffer,
1955 bw,
1956 bh,
1957 0x0A1018,
1958 false,
1959 std::slice::from_ref(&corners),
1960 );
1961 for seg in corners.windows(2) {
1962 crate::gfx::raster::draw_line_aa(
1963 &mut gfx.buffer,
1964 bw,
1965 bh,
1966 0x00D2FF,
1967 false,
1968 seg[0][0],
1969 seg[0][1],
1970 seg[1][0],
1971 seg[1][1],
1972 );
1973 }
1974 }
1975 let px = 22.0f32;
1977 let pad = 20.0f32;
1978 let line_h = px * 1.45;
1979 let mut cx = x + pad;
1980 let mut cy = y + pad;
1981 let use_font = font >= 0 && (font as usize) < self.fonts.len();
1982 for (text, role, nl) in &runs {
1983 if *nl {
1984 cx = x + pad;
1985 cy += line_h;
1986 }
1987 for word in text.split_inclusive(' ') {
1988 let wpx = if use_font {
1989 self.fonts[font as usize].measure(word, px)
1990 } else {
1991 ling_ui::holo::text_width(word, px * 0.6, px * 0.24)
1992 };
1993 if cx + wpx > x + w - pad && cx > x + pad + 1.0 {
1994 cx = x + pad;
1995 cy += line_h;
1996 }
1997 if cy + line_h > y + h {
1998 break;
1999 }
2000 let col = colors[(*role).min(3)];
2001 if use_font {
2002 let glyphs = self.font_layout_2d_glyphs(font as usize, cx, cy, px, word);
2003 let mut gfx = self.gfx.borrow_mut();
2004 let (bw, bh, add) = (gfx.width, gfx.height, gfx.blend == 1);
2005 for contours in &glyphs {
2006 crate::gfx::raster::fill_contours_aa(
2007 &mut gfx.buffer,
2008 bw,
2009 bh,
2010 col,
2011 add,
2012 contours,
2013 );
2014 }
2015 } else {
2016 let segs = ling_ui::holo::text_lines(word, cx, cy, px * 0.6, px, px * 0.24);
2017 let mut gfx = self.gfx.borrow_mut();
2018 let (bw, bh) = (gfx.width, gfx.height);
2019 for s in segs {
2020 draw_line(&mut gfx.buffer, bw, bh, col, s[0], s[1], s[2], s[3]);
2021 }
2022 }
2023 cx += wpx;
2024 }
2025 }
2026 if !typing && (t * 3.0).sin() > 0.0 {
2028 let ax = x + w - 26.0;
2029 let ay = y + h - 22.0;
2030 let mut gfx = self.gfx.borrow_mut();
2031 let (bw, bh) = (gfx.width, gfx.height);
2032 crate::gfx::raster::fill_contours_aa(
2033 &mut gfx.buffer,
2034 bw,
2035 bh,
2036 0x00D2FF,
2037 false,
2038 std::slice::from_ref(&vec![
2039 [ax - 7.0, ay],
2040 [ax + 7.0, ay],
2041 [ax, ay + 9.0],
2042 [ax - 7.0, ay],
2043 ]),
2044 );
2045 }
2046 }
2047
2048 #[cfg(not(target_arch = "wasm32"))]
2051 fn ensure_music(&mut self) -> bool {
2052 if self.music.is_some() {
2053 return true;
2054 }
2055 if self.music_init {
2056 return false;
2057 }
2058 self.music_init = true;
2059 match ling_music::MusicEngine::new() {
2060 Ok(m) => {
2061 self.music = Some(m);
2062 true
2063 },
2064 Err(e) => {
2065 eprintln!("music engine init failed (no music playback): {e}");
2066 false
2067 },
2068 }
2069 }
2070
2071 #[cfg(target_arch = "wasm32")]
2072 fn wasm_resolve_source_path(&self, path: &str) -> String {
2073 let p = path.trim();
2074 if p.is_empty() {
2075 return String::new();
2076 }
2077 if p.contains("://") || p.starts_with('/') || p.starts_with("./") || p.starts_with("../") {
2078 return p.to_string();
2079 }
2080 if let Some(d) = &self.source_dir {
2081 let base = d.to_string_lossy().replace('\\', "/");
2082 if !base.is_empty() {
2083 return format!(
2084 "{}/{}",
2085 base.trim_end_matches('/'),
2086 p.trim_start_matches("./")
2087 );
2088 }
2089 }
2090 p.to_string()
2091 }
2092
2093 #[cfg(target_arch = "wasm32")]
2094 fn wasm_music_builtin(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, EvalErr> {
2095 match name {
2096 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" =>
2098 {
2099 let path = self.arg_str(args, 0, "");
2100 let resolved = self.wasm_resolve_source_path(&path);
2101 match wasm_fetch_bytes(&resolved)
2102 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
2103 {
2104 Ok(t) => {
2105 let id = self.tracks.len();
2106 self.tracks.push(t);
2107 return Ok(Some(Value::Number(id as f64)));
2108 },
2109 Err(e) => {
2110 eprintln!("music_load failed ({path}): {e}");
2111 return Ok(Some(Value::Number(-1.0)));
2112 },
2113 }
2114 },
2115 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" =>
2116 {
2117 let id = self.arg_num(args, 0, 0.0)? as i64;
2118 let d = self
2119 .tracks
2120 .get(id as usize)
2121 .map(|t| t.duration)
2122 .unwrap_or(0.0);
2123 return Ok(Some(Value::Number(d as f64)));
2124 },
2125 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" =>
2126 {
2127 let id = self.arg_num(args, 0, 0.0)? as i64;
2128 let b = self
2129 .tracks
2130 .get(id as usize)
2131 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
2132 .unwrap_or(0.0);
2133 return Ok(Some(Value::Number(b as f64)));
2134 },
2135 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" => {
2136 let id = self.arg_num(args, 0, 0.0)? as i64;
2137 let k = self
2138 .tracks
2139 .get(id as usize)
2140 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
2141 .unwrap_or_default();
2142 return Ok(Some(Value::Str(k)));
2143 },
2144 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" =>
2145 {
2146 let id = self.arg_num(args, 0, 0.0)? as i64;
2147 let v = self
2148 .tracks
2149 .get(id as usize)
2150 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
2151 .unwrap_or_default();
2152 return Ok(Some(Value::List(
2153 v.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2154 )));
2155 },
2156 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" =>
2157 {
2158 let id = self.arg_num(args, 0, 0.0)? as i64;
2159 let beats = self
2160 .tracks
2161 .get(id as usize)
2162 .map(|t| {
2163 let b = ling_music::analysis::bpm(&t.mono, t.rate);
2164 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
2165 })
2166 .unwrap_or_default();
2167 return Ok(Some(Value::List(
2168 beats.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2169 )));
2170 },
2171 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" =>
2172 {
2173 let path = self.arg_str(args, 0, "");
2174 let resolved = self.wasm_resolve_source_path(&path);
2175 match wasm_fetch_text(&resolved) {
2176 Ok(text) => {
2177 let id = self.lyrics.len();
2178 self.lyrics.push(ling_music::Lyrics::parse(&text));
2179 return Ok(Some(Value::Number(id as f64)));
2180 },
2181 Err(e) => {
2182 eprintln!("music_lrc failed ({path}): {e}");
2183 return Ok(Some(Value::Number(-1.0)));
2184 },
2185 }
2186 },
2187 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" =>
2188 {
2189 let id = self.arg_num(args, 0, 0.0)? as i64;
2190 let t = self.arg_num(args, 1, 0.0)? as f32;
2191 let line = self
2192 .lyrics
2193 .get(id as usize)
2194 .map(|l| l.line_at(t).to_string())
2195 .unwrap_or_default();
2196 return Ok(Some(Value::Str(line)));
2197 },
2198 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" =>
2199 {
2200 let path = self.arg_str(args, 0, "");
2201 let resolved = self.wasm_resolve_source_path(&path);
2202 match wasm_fetch_bytes(&resolved).and_then(|bytes| {
2203 ling_music::midi::from_bytes(&bytes).map_err(|e| e.to_string())
2204 }) {
2205 Ok(m) => {
2206 let id = self.midis.len();
2207 self.midis.push(m);
2208 return Ok(Some(Value::Number(id as f64)));
2209 },
2210 Err(e) => {
2211 eprintln!("music_midi_load failed ({path}): {e}");
2212 return Ok(Some(Value::Number(-1.0)));
2213 },
2214 }
2215 },
2216 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" =>
2217 {
2218 let id = self.arg_num(args, 0, 0.0)? as i64;
2219 let n = self
2220 .midis
2221 .get(id as usize)
2222 .map(|m| m.notes.len())
2223 .unwrap_or(0);
2224 return Ok(Some(Value::Number(n as f64)));
2225 },
2226 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" =>
2227 {
2228 let id = self.arg_num(args, 0, 0.0)? as i64;
2229 let mut out = Vec::new();
2230 if let Some(m) = self.midis.get(id as usize) {
2231 for n in &m.notes {
2232 out.push(Value::Number(n.time as f64));
2233 out.push(Value::Number(n.midi as f64));
2234 }
2235 }
2236 return Ok(Some(Value::List(out.into())));
2237 },
2238 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" =>
2239 {
2240 let id = self.arg_num(args, 0, 0.0)? as i64;
2241 let mut out = Vec::new();
2242 if let Some(m) = self.midis.get(id as usize) {
2243 for n in &m.notes {
2244 out.push(Value::Number(n.time as f64));
2245 out.push(Value::Number(n.midi as f64));
2246 out.push(Value::Number(n.dur as f64));
2247 }
2248 }
2249 return Ok(Some(Value::List(out.into())));
2250 },
2251 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" =>
2252 {
2253 let delta_ms = self.arg_num(args, 0, 9999.0)? as f32;
2254 return Ok(Some(Value::Number(
2255 ling_music::Grade::judge(delta_ms).index() as f64,
2256 )));
2257 },
2258 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" =>
2259 {
2260 let idx = self.arg_num(args, 0, 4.0)? as i32;
2261 return Ok(Some(Value::Str(
2262 ling_music::Grade::from_index(idx).name().to_string(),
2263 )));
2264 },
2265 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" =>
2266 {
2267 let hz = self.arg_num(args, 0, 0.0)? as f32;
2268 return Ok(Some(Value::Str(ling_music::note::hz_to_name(hz))));
2269 },
2270 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" =>
2271 {
2272 let midi = match args.get(0) {
2273 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(69),
2274 Some(Value::Number(n)) => *n as i32,
2275 _ => 69,
2276 };
2277 return Ok(Some(Value::Number(
2278 ling_music::note::midi_to_hz(midi as f32) as f64,
2279 )));
2280 },
2281 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" =>
2282 {
2283 let hz = self.arg_num(args, 0, 0.0)? as f32;
2284 let target = self.arg_num(args, 1, 0.0)? as f32;
2285 return Ok(Some(Value::Number(
2286 ling_music::karaoke::pitch_score(hz, target) as f64,
2287 )));
2288 },
2289
2290 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" =>
2292 {
2293 let id = self.arg_num(args, 0, 0.0)? as usize;
2294 if let Some(t) = self.tracks.get(id) {
2295 crate::gfx::audio_web::play_music(id, &t.stereo, t.channels, t.rate, 1.0);
2296 }
2297 return Ok(Some(Value::Unit));
2298 },
2299 "music_pause"
2300 | "暂停音乐"
2301 | "音楽一時停止"
2302 | "음악일시정지"
2303 | "หยุดเพลงชั่วคราว"
2304 | "music_stop"
2305 | "停止音乐"
2306 | "音楽停止"
2307 | "음악정지"
2308 | "หยุดเพลง" => {
2309 let id = self.arg_num(args, 0, 0.0)? as usize;
2310 crate::gfx::audio_web::stop_music(id);
2311 return Ok(Some(Value::Unit));
2312 },
2313 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" =>
2314 {
2315 return Ok(Some(Value::Unit));
2317 },
2318 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" =>
2319 {
2320 return Ok(Some(Value::Number(
2321 crate::gfx::audio_web::current_music_position(),
2322 )));
2323 },
2324 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" =>
2325 {
2326 let vol = self.arg_num(args, 0, 0.8)? as f32;
2327 crate::gfx::audio_web::set_music_volume(0, vol);
2329 return Ok(Some(Value::Unit));
2330 },
2331
2332 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" =>
2334 {
2335 let id = self.arg_num(args, 0, 0.0)? as usize;
2336 let nbands = self.arg_num(args, 1, 16.0)? as usize;
2337 let pos = crate::gfx::audio_web::current_music_position() as f32;
2338 let bands = if let Some(t) = self.tracks.get(id) {
2339 ling_music::analysis::fft_bands_at_pos(&t.mono, t.rate, pos, nbands)
2340 } else {
2341 vec![0.0f32; nbands]
2342 };
2343 return Ok(Some(Value::List(
2344 bands.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2345 )));
2346 },
2347
2348 _ => {},
2349 }
2350 Ok(None)
2351 }
2352
2353 #[cfg(not(target_arch = "wasm32"))]
2357 fn font_layout_2d(
2358 &mut self,
2359 id: usize,
2360 x: f32,
2361 y: f32,
2362 px: f32,
2363 text: &str,
2364 ) -> Vec<Vec<[f32; 2]>> {
2365 let mut out = Vec::new();
2366 for g in self.font_layout_2d_glyphs(id, x, y, px, text) {
2367 out.extend(g);
2368 }
2369 out
2370 }
2371
2372 #[cfg(not(target_arch = "wasm32"))]
2375 fn font_layout_2d_glyphs(
2376 &mut self,
2377 id: usize,
2378 x: f32,
2379 y: f32,
2380 px: f32,
2381 text: &str,
2382 ) -> Vec<Vec<Vec<[f32; 2]>>> {
2383 let font = &mut self.fonts[id];
2384 let asc = font.ascent();
2385 let tol = 0.3 / px;
2386 let mut pen = 0.0f32;
2387 let mut glyphs = Vec::new();
2388 for ch in text.chars() {
2389 let go = font.glyph_outline(ch, tol);
2390 let mut contours = Vec::with_capacity(go.polylines.len());
2391 for pl in &go.polylines {
2392 let mapped: Vec<[f32; 2]> = pl
2393 .iter()
2394 .map(|p| [x + (pen + p[0]) * px, y + (asc - p[1]) * px])
2395 .collect();
2396 contours.push(mapped);
2397 }
2398 glyphs.push(contours);
2399 pen += go.advance;
2400 }
2401 glyphs
2402 }
2403
2404 pub fn register_program(&mut self, program: &Program) -> Result<(), String> {
2409 for item in &program.items {
2410 self.register_item("", item)?;
2411 }
2412 let mut env = new_env();
2413 let non_do: Vec<_> = self
2414 .globals
2415 .iter()
2416 .filter(|(_, e)| !matches!(e, Expr::Do(_)))
2417 .map(|(k, e)| (k.clone(), e.clone()))
2418 .collect();
2419 let mut pending: Vec<(String, Expr)> = Vec::new();
2420 for (k, expr) in &non_do {
2421 let mut tmp = new_env();
2422 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2423 env.insert(k.clone(), v);
2424 } else {
2425 pending.push((k.clone(), expr.clone()));
2426 }
2427 }
2428 for (k, expr) in &pending {
2429 let mut tmp = env.clone();
2430 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2431 env.insert(k.clone(), v);
2432 }
2433 }
2434 self.global_seed = env;
2435 Ok(())
2436 }
2437
2438 pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
2439 self.register_program(program)?;
2440 let entry = self
2441 .find_entry()
2442 .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
2443 let mut env = self.global_seed.clone();
2444 self.framed("start", |me| me.eval_expr(&entry, &mut env))
2445 .map(|_| ())
2446 .map_err(|e| match e {
2447 EvalErr::Runtime(s) => s,
2448 EvalErr::Return(_) => "unexpected top-level return".to_string(),
2449 EvalErr::Break => "unexpected break at top level".to_string(),
2450 })
2451 }
2452
2453 fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
2454 match item {
2455 Item::Bind(name, expr) => {
2456 let key = if ns.is_empty() {
2457 name.clone()
2458 } else {
2459 format!("{ns}::{name}")
2460 };
2461 self.globals.insert(key, expr.clone());
2462 },
2463 Item::Fn(def) => {
2464 let key = if ns.is_empty() {
2465 def.name.clone()
2466 } else {
2467 format!("{ns}::{}", def.name)
2468 };
2469 self.functions.insert(key, Rc::new(def.clone()));
2470 },
2471 Item::Mod(name, body) => {
2472 let child_ns = if ns.is_empty() {
2473 name.clone()
2474 } else {
2475 format!("{ns}::{name}")
2476 };
2477 for child in body {
2478 self.register_item(&child_ns, child)?;
2479 }
2480 },
2481 Item::TypeAlias(_, _) => {},
2482 Item::Struct(name, fields) => {
2483 self.structs.insert(name.clone(), fields.clone());
2484 if !ns.is_empty() {
2485 self.structs.insert(format!("{ns}::{name}"), fields.clone());
2486 }
2487 },
2488 Item::Enum(name, variants) => {
2489 for v in variants {
2490 self.enum_variants
2491 .insert(v.name.clone(), (name.clone(), v.arity));
2492 self.enum_variants
2493 .insert(format!("{name}::{}", v.name), (name.clone(), v.arity));
2494 if !ns.is_empty() {
2495 self.enum_variants
2496 .insert(format!("{ns}::{name}::{}", v.name), (name.clone(), v.arity));
2497 }
2498 }
2499 },
2500 Item::Use { path, alias } => {
2501 self.load_module(path, alias.as_deref(), ns)?;
2502 },
2503 }
2504 Ok(())
2505 }
2506
2507 fn load_module(
2512 &mut self,
2513 path: &str,
2514 alias: Option<&str>,
2515 parent_ns: &str,
2516 ) -> Result<(), String> {
2517 #[cfg(target_arch = "wasm32")]
2519 let (source, sub_dir) = {
2520 if self.loaded_files.contains(path) {
2522 return Ok(());
2523 }
2524 self.loaded_files.insert(path.to_string());
2525
2526 let src = crate::runtime::get_wasm_module(path)
2527 .or_else(|| crate::runtime::get_wasm_module(&format!("{}.ling", path)))
2528 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2529 (src, None::<std::path::PathBuf>)
2530 };
2531
2532 #[cfg(not(target_arch = "wasm32"))]
2534 let (source, sub_dir) = {
2535 let base_dir = self
2536 .source_dir
2537 .clone()
2538 .unwrap_or_else(|| std::path::PathBuf::from("."));
2539 let raw = std::path::Path::new(path);
2540 let candidates: Vec<std::path::PathBuf> = vec![
2541 base_dir.join(format!("{}.ling", path)),
2542 base_dir.join(format!("{}.灵", path)),
2543 base_dir.join(format!("{}.령", path)),
2544 base_dir.join(format!("{}.霊", path)),
2545 base_dir.join(format!("{}.ลิง", path)),
2546 base_dir.join(raw),
2547 std::path::PathBuf::from(format!("{}.ling", path)),
2548 std::path::PathBuf::from(path),
2549 ];
2550
2551 let resolved = candidates
2552 .into_iter()
2553 .find(|p| p.exists())
2554 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2555
2556 let canonical = resolved
2557 .canonicalize()
2558 .unwrap_or_else(|_| resolved.clone())
2559 .to_string_lossy()
2560 .to_string();
2561
2562 if self.loaded_files.contains(&canonical) {
2564 return Ok(());
2565 }
2566 self.loaded_files.insert(canonical.clone());
2567
2568 let src = std::fs::read_to_string(&resolved)
2569 .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
2570 let dir = resolved.parent().map(|p| p.to_path_buf());
2571 (src, dir)
2572 };
2573
2574 let program = crate::parser::parse(&source)
2575 .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
2576
2577 let target_ns = match (parent_ns.is_empty(), alias) {
2579 (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
2580 (_, Some(a)) => a.to_string(),
2581 (false, None) => parent_ns.to_string(),
2582 (true, None) => String::new(),
2583 };
2584
2585 let prev_dir = self.source_dir.clone();
2587 self.source_dir = sub_dir;
2588
2589 for item in &program.items {
2590 self.register_item(&target_ns, item)?;
2591 }
2592
2593 self.source_dir = prev_dir;
2594 Ok(())
2595 }
2596
2597 fn find_entry(&self) -> Option<Expr> {
2598 for key in crate::entry::ENTRY_NAMES {
2600 if let Some(e) = self.globals.get(*key) {
2601 return Some(e.clone());
2602 }
2603 }
2604 self.globals
2605 .values()
2606 .find(|e| matches!(e, Expr::Do(_)))
2607 .cloned()
2608 }
2609
2610 fn eval_expr(&mut self, expr: &Expr, env: &mut Env) -> EvalResult {
2613 match expr {
2614 Expr::Str(s) => Ok(Value::Str(s.clone())),
2615 Expr::Number(n) => Ok(Value::Number(*n)),
2616 Expr::Bool(b) => Ok(Value::Bool(*b)),
2617 Expr::Unit => Ok(Value::Unit),
2618 Expr::Array(elems) => {
2619 let vs: Vec<_> = elems
2620 .iter()
2621 .map(|e| self.eval_expr(e, env))
2622 .collect::<Result<_, _>>()?;
2623 Ok(Value::List(Rc::new(vs)))
2624 },
2625
2626 Expr::Ident(name) => self.lookup(name, env),
2627
2628 Expr::Path(segs) => {
2629 if segs.len() == 1 {
2630 return self.lookup(&segs[0], env);
2631 }
2632 Ok(Value::Str(segs.join("::")))
2633 },
2634
2635 Expr::Ref(inner) => self.eval_expr(inner, env),
2636 Expr::Await(inner) => self.eval_expr(inner, env),
2637
2638 Expr::Do(stmts) => {
2639 let mut local = env.clone();
2640 Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
2641 },
2642
2643 Expr::BinOp(op, lhs, rhs) => {
2644 let l = self.eval_expr(lhs, env)?;
2645 let r = self.eval_expr(rhs, env)?;
2646 self.apply_binop(op, l, r)
2647 },
2648
2649 Expr::If { cond, then, elseifs, else_body } => {
2650 let cond_val = self.eval_expr(cond, env)?;
2651 if self.is_truthy(&cond_val) {
2652 return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
2653 }
2654 for (ei_cond, ei_body) in elseifs {
2655 let ei_cond_val = self.eval_expr(ei_cond, env)?;
2656 if self.is_truthy(&ei_cond_val) {
2657 return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
2658 }
2659 }
2660 if let Some(eb) = else_body {
2661 return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
2662 }
2663 Ok(Value::Unit)
2664 },
2665
2666 Expr::While { cond, body } => {
2667 loop {
2671 let cv = self.eval_expr(cond, env)?;
2672 if !self.is_truthy(&cv) {
2673 break;
2674 }
2675 match self.exec_block(body, env) {
2676 Ok(_) => {},
2677 Err(EvalErr::Break) => break,
2678 Err(e) => return Err(e),
2679 }
2680 }
2681 Ok(Value::Unit)
2682 },
2683
2684 Expr::For { var, iter, body } => {
2685 let iter_val = self.eval_expr(iter, env)?;
2686 let items = self.value_to_iter(iter_val)?;
2687 for item in items {
2688 let mut local = env.clone();
2689 local.insert(var.clone(), item);
2690 match self.exec_block(body, &mut local) {
2691 Ok(_) => {},
2692 Err(EvalErr::Break) => break,
2693 Err(e) => return Err(e),
2694 }
2695 }
2696 Ok(Value::Unit)
2697 },
2698
2699 Expr::Match(subject, arms) => {
2700 let subj = self.eval_expr(subject, env)?;
2701 for arm in arms {
2702 if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
2703 let mut local = env.clone();
2704 local.extend(bindings);
2705 return self.eval_expr(&arm.body, &mut local);
2706 }
2707 }
2708 Ok(Value::Unit)
2709 },
2710
2711 Expr::Range(lo, hi) => {
2712 let lo_v = self.eval_expr(lo, env)?;
2713 let hi_v = self.eval_expr(hi, env)?;
2714 let lo_n = self.to_number(&lo_v)? as i64;
2715 let hi_n = self.to_number(&hi_v)? as i64;
2716 Ok(Value::List(Rc::new(
2717 (lo_n..hi_n).map(|i| Value::Number(i as f64)).collect(),
2718 )))
2719 },
2720
2721 Expr::Index(base, idx) => {
2722 let b = self.eval_expr(base, env)?;
2723 let i = self.eval_expr(idx, env)?;
2724 let n = self.to_number(&i)? as usize;
2725 match b {
2726 Value::List(v) => v
2727 .get(n)
2728 .cloned()
2729 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2730 Value::Str(s) => s
2731 .chars()
2732 .nth(n)
2733 .map(|c| Value::Str(c.to_string()))
2734 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2735 other => Err(EvalErr::from(format!("cannot index {:?}", other))),
2736 }
2737 },
2738
2739 Expr::Call(callee, args) => {
2740 let arg_vals: Vec<Value> = args
2741 .iter()
2742 .map(|a| self.eval_expr(a, env))
2743 .collect::<Result<_, _>>()?;
2744 match callee.as_ref() {
2745 Expr::Ident(name) => self.call_named(name, arg_vals, env),
2746 Expr::Path(segs) => self.call_named(&segs.join("::"), arg_vals, env),
2747 _ => {
2748 let v = self.eval_expr(callee, env)?;
2749 self.call_value(v, arg_vals)
2750 },
2751 }
2752 },
2753
2754 Expr::MethodCall { receiver, method, args } => {
2755 let recv = self.eval_expr(receiver, env)?;
2756 let arg_vals: Vec<Value> = args
2757 .iter()
2758 .map(|a| self.eval_expr(a, env))
2759 .collect::<Result<_, _>>()?;
2760 self.call_method(recv, method, arg_vals)
2761 },
2762
2763 Expr::Closure(params, body) => Ok(Value::Fn(
2764 params.clone(),
2765 vec![Stmt::Expr(*body.clone())],
2766 env.clone(),
2767 )),
2768
2769 Expr::Asm(_) => Ok(Value::Unit),
2770 }
2771 }
2772
2773 fn exec_block(&mut self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
2776 let mut last: Option<Value> = None;
2777 for stmt in stmts {
2778 match stmt {
2779 Stmt::Bind(name, expr) => {
2780 match self.try_inplace_list_update(name, expr, env)? {
2781 Some(v) => env.insert(name.clone(), v),
2782 None => {
2783 let v = self.eval_expr(expr, env)?;
2784 env.insert(name.clone(), v)
2785 },
2786 };
2787 last = None;
2788 },
2789 Stmt::Return(expr) => {
2790 let v = self.eval_expr(expr, env)?;
2791 return Err(EvalErr::Return(v));
2792 },
2793 Stmt::Expr(expr) => {
2794 last = Some(self.eval_expr(expr, env)?);
2795 },
2796 }
2797 }
2798 Ok(last)
2799 }
2800
2801 fn try_inplace_list_update(
2808 &mut self,
2809 name: &str,
2810 expr: &Expr,
2811 env: &mut Env,
2812 ) -> Result<Option<Value>, EvalErr> {
2813 let Expr::Call(callee, args) = expr else { return Ok(None) };
2814 let Expr::Ident(fname) = callee.as_ref() else { return Ok(None) };
2815 let is_push = matches!(
2816 fname.as_str(),
2817 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가"
2818 );
2819 let is_set = matches!(
2820 fname.as_str(),
2821 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정"
2822 );
2823 if !is_push && !is_set {
2824 return Ok(None);
2825 }
2826 match args.first() {
2829 Some(Expr::Ident(a0)) if a0 == name => {},
2830 _ => return Ok(None),
2831 }
2832 if self.functions.contains_key(fname.as_str()) {
2833 return Ok(None);
2834 }
2835 if is_push {
2836 if args.len() != 2 {
2837 return Ok(None);
2838 }
2839 let val = self.eval_expr(&args[1], env)?;
2840 match env.remove(name) {
2841 Some(Value::List(mut v)) => {
2842 Rc::make_mut(&mut v).push(val);
2843 Ok(Some(Value::List(v)))
2844 },
2845 other => {
2846 if let Some(o) = other {
2847 env.insert(name.to_string(), o);
2848 }
2849 Ok(None)
2850 },
2851 }
2852 } else {
2853 if args.len() != 3 {
2854 return Ok(None);
2855 }
2856 let idx_v = self.eval_expr(&args[1], env)?;
2857 let idx = self.to_number(&idx_v).unwrap_or(0.0) as usize;
2858 let val = self.eval_expr(&args[2], env)?;
2859 match env.remove(name) {
2860 Some(Value::List(mut v)) => {
2861 if idx < v.len() {
2862 Rc::make_mut(&mut v)[idx] = val;
2863 }
2864 Ok(Some(Value::List(v)))
2865 },
2866 other => {
2867 if let Some(o) = other {
2868 env.insert(name.to_string(), o);
2869 }
2870 Ok(None)
2871 },
2872 }
2873 }
2874 }
2875
2876 fn lookup(&self, name: &str, env: &Env) -> EvalResult {
2879 if let Some(v) = env.get(name) {
2880 return Ok(v.clone());
2881 }
2882 if let Some(v) = self.global_seed.get(name) {
2885 return Ok(v.clone());
2886 }
2887 if self.functions.contains_key(name) {
2888 let def = &self.functions[name];
2889 return Ok(Value::Fn(def.params.clone(), def.body.clone(), new_env()));
2890 }
2891 if let Some((enum_name, 0)) = self.enum_variants.get(name).cloned() {
2893 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
2894 return Ok(Value::Variant { enum_name, variant, payload: Vec::new() });
2895 }
2896 match name {
2898 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" => {
2899 return Ok(Value::Number(std::f64::consts::PI))
2900 },
2901 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" => {
2902 return Ok(Value::Number(std::f64::consts::TAU))
2903 },
2904 _ => {},
2905 }
2906 Err(EvalErr::from(format!("undefined: '{name}'")))
2907 }
2908
2909 pub(crate) fn call_named(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2918 if !ling_profile_enabled() {
2919 return self.call_named_inner(name, args, env);
2920 }
2921 let t0 = crate::runtime::now_secs();
2922 let r = self.call_named_inner(name, args, env);
2923 ling_profile_record(
2924 name,
2925 ((crate::runtime::now_secs() - t0) * 1_000_000_000.0) as u128,
2926 );
2927 r
2928 }
2929
2930 fn call_named_inner(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2931 if let Some(def) = self.functions.get(name).cloned() {
2934 let mut call_env =
2935 FxHashMap::with_capacity_and_hasher(def.params.len(), Default::default());
2936 let _ = env; for (param, arg) in def.params.iter().zip(args) {
2938 call_env.insert(param.clone(), arg);
2939 }
2940 return match self.framed(name, |me| me.exec_block(&def.body, &mut call_env)) {
2941 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
2942 Err(EvalErr::Return(v)) => Ok(v),
2943 Err(e) => Err(e),
2944 };
2945 }
2946
2947 #[cfg(target_arch = "wasm32")]
2948 if let Some(v) = self.wasm_music_builtin(name, &args)? {
2949 return Ok(v);
2950 }
2951
2952 match name {
2953 "__ling_global" => {
2956 if let Some(Value::Str(g)) = args.first() {
2957 if let Some(v) = self.global_seed.get(g.as_str()) {
2958 return Ok(v.clone());
2959 }
2960 }
2961 return Ok(Value::Unit);
2962 },
2963 "print" | "println" | "印" | "打印" | "印刷" | "พิมพ์" | "출력" | "вывести"
2965 | "imprimir" | "afficher" => {
2966 let s = args
2967 .iter()
2968 .map(|v| v.to_string())
2969 .collect::<Vec<_>>()
2970 .join("");
2971 println!("{s}");
2972 return Ok(Value::Unit);
2973 },
2974 "print_color" | "พิมพ์สี" => {
2977 #[cfg(windows)]
2978 {
2979 use std::sync::Once;
2980 static VT: Once = Once::new();
2981 VT.call_once(|| {
2982 extern "system" {
2983 fn GetStdHandle(n: u32) -> *mut std::ffi::c_void;
2984 fn GetConsoleMode(h: *mut std::ffi::c_void, m: *mut u32) -> i32;
2985 fn SetConsoleMode(h: *mut std::ffi::c_void, m: u32) -> i32;
2986 }
2987 unsafe {
2988 let h = GetStdHandle(0xFFFF_FFF5u32); let mut mode = 0u32;
2990 if GetConsoleMode(h, &mut mode) != 0 {
2991 SetConsoleMode(h, mode | 0x0004); }
2993 }
2994 });
2995 }
2996 let col = self.arg_num(&args, 0, 7.0)? as i64;
2997 let s = args
2998 .iter()
2999 .skip(1)
3000 .map(|v| v.to_string())
3001 .collect::<Vec<_>>()
3002 .join("");
3003 let code = 90 + col.clamp(0, 7);
3004 println!("\x1b[1;{code}m{s}\x1b[0m");
3005 return Ok(Value::Unit);
3006 },
3007 "format"
3009 | "格式"
3010 | "フォーマット"
3011 | "서식"
3012 | "รูปแบบ"
3013 | "форматировать"
3014 | "formatear"
3015 | "formater" => {
3016 return Ok(Value::Str(self.builtin_format(&args)?));
3017 },
3018 "格式::拼接" | "format::join" => match args.first() {
3020 Some(Value::List(items)) => {
3021 return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
3022 },
3023 _ => return Ok(Value::Str(self.builtin_format(&args)?)),
3024 },
3025 "ok" | "好" | "良し" | "좋아" | "โอเค" => {
3027 let val = args.into_iter().next().unwrap_or(Value::Unit);
3028 return Ok(Value::Ok(Box::new(val)));
3029 },
3030 "bad" | "坏" | "err" | "悪い" | "나쁨" | "ผิด" => {
3031 let val = args.into_iter().next().unwrap_or(Value::Unit);
3032 return Ok(Value::Err(Box::new(val)));
3033 },
3034 "向量::从" | "Vec::from" => {
3036 if let Some(Value::List(v)) = args.first() {
3037 return Ok(Value::List(v.clone()));
3038 }
3039 return Ok(Value::List(Rc::new(args)));
3040 },
3041 "向量::有容量" | "Vec::with_capacity" => {
3042 return Ok(Value::List(Rc::new(Vec::new())))
3043 },
3044 "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
3046 "计时::现在" | "Timer::now" => return Ok(Value::Number(1000.0)),
3047 "sleep" | "หยุด" | "นอน" | "sleep_ms" | "睡眠" | "眠る" | "スリープ" | "잠자기"
3049 | "잠" | "流水::睡眠" | "Flow::sleep" => {
3050 if let Some(ms_val) = args.first() {
3051 if let Ok(ms) = self.to_number(ms_val) {
3052 #[cfg(target_arch = "wasm32")]
3053 wasm_sleep_ms(ms.max(0.0) as i32);
3054 #[cfg(not(target_arch = "wasm32"))]
3055 std::thread::sleep(std::time::Duration::from_millis(ms as u64));
3056 }
3057 }
3058 return Ok(Value::Unit);
3059 },
3060 "流水::并行" | "Flow::parallel" => {
3062 if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
3063 let _ = params;
3064 match self.exec_block(&body, &mut cap) {
3065 Ok(Some(v)) => return Ok(v),
3066 Ok(None) => return Ok(Value::Unit),
3067 Err(EvalErr::Return(v)) => return Ok(v),
3068 Err(e) => return Err(e),
3069 }
3070 }
3071 return Ok(Value::Unit);
3072 },
3073
3074 "sin" | "ไซน์" | "正弦" | "サイン" | "사인" => {
3083 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
3084 },
3085 "cos" | "โคไซน์" | "余弦" | "コサイン" | "코사인" => {
3086 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
3087 },
3088
3089 "tanh" | "tanhf" | "双曲正切" | "双曲線正接" | "쌍곡탄젠트" => {
3092 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
3093 },
3094
3095 "tan" | "แทนเจนต์" | "正切" | "タンジェント" | "탄젠트" => {
3096 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
3097 },
3098 "asin" | "arcsin" | "反正弦" | "アークサイン" | "아크사인" | "อาร์กไซน์" =>
3099 {
3100 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
3101 },
3102 "acos" | "arccos" | "反余弦" | "アークコサイン" | "아크코사인" | "อาร์กโคไซน์" =>
3103 {
3104 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
3105 },
3106 "atan" | "arctan" | "反正切" | "アークタンジェント" | "아크탄젠트" | "อาร์กแทนเจนต์" =>
3107 {
3108 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
3109 },
3110 "atan2" | "arctan2" | "反正切2" | "アークタンジェント2" | "아크탄젠트2" =>
3111 {
3112 let y = self.arg_num(&args, 0, 0.0)?;
3113 let x = self.arg_num(&args, 1, 1.0)?;
3114 return Ok(Value::Number(y.atan2(x)));
3115 },
3116
3117 "sqrt" | "รากที่สอง" | "平方根" | "根" | "제곱근" => {
3119 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
3120 },
3121 "cbrt" | "立方根" | "세제곱근" | "รากที่สาม" => {
3122 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
3123 },
3124 "pow" | "ยกกำลัง" | "幂" | "べき乗" | "거듭제곱" => {
3125 let base = self.arg_num(&args, 0, 0.0)?;
3126 let exp = self.arg_num(&args, 1, 1.0)?;
3127 return Ok(Value::Number(base.powf(exp)));
3128 },
3129 "exp" | "指数" | "指数関数" | "지수" => {
3130 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
3131 },
3132 "hypot" | "斜边" | "斜辺" | "빗변" => {
3133 let x = self.arg_num(&args, 0, 0.0)?;
3134 let y = self.arg_num(&args, 1, 0.0)?;
3135 return Ok(Value::Number(x.hypot(y)));
3136 },
3137
3138 "ln" | "log" | "ลอการิทึม" | "对数" | "対数" | "로그" => {
3140 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
3141 },
3142 "log2" | "对数2" | "対数2" | "로그2" => {
3143 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
3144 },
3145 "log10" | "对数10" | "対数10" | "로그10" => {
3146 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
3147 },
3148
3149 "abs" | "ค่าสัมบูรณ์" | "绝对值" | "绝对" | "絶対値" | "절댓값" | "절대값" =>
3151 {
3152 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
3153 },
3154 "floor" | "ปัดลง" | "向下取整" | "下整" | "床関数" | "내림" => {
3155 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
3156 },
3157 "ceil" | "ปัดขึ้น" | "向上取整" | "上整" | "天井関数" | "올림" =>
3158 {
3159 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
3160 },
3161 "round" | "ปัดเศษ" | "四舍五入" | "四舍" | "四捨五入" | "반올림" =>
3162 {
3163 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
3164 },
3165 "trunc"
3166 | "int"
3167 | "ตัดทศนิยม"
3168 | "取整"
3169 | "整数化"
3170 | "整数"
3171 | "截整"
3172 | "정수화"
3173 | "정수"
3174 | "切り捨て"
3175 | "버림" => {
3176 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
3177 },
3178 "fract" | "小数部分" | "小数部" | "소수부" => {
3179 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
3180 },
3181
3182 "min" | "ต่ำสุด" | "最小" | "최솟값" => {
3184 let a = self.arg_num(&args, 0, 0.0)?;
3185 let b = self.arg_num(&args, 1, 0.0)?;
3186 return Ok(Value::Number(a.min(b)));
3187 },
3188 "max" | "สูงสุด" | "最大" | "최댓값" => {
3189 let a = self.arg_num(&args, 0, 0.0)?;
3190 let b = self.arg_num(&args, 1, 0.0)?;
3191 return Ok(Value::Number(a.max(b)));
3192 },
3193 "clamp" | "จำกัด" | "截取" | "範囲制限" | "범위제한" => {
3194 let x = self.arg_num(&args, 0, 0.0)?;
3195 let lo = self.arg_num(&args, 1, 0.0)?;
3196 let hi = self.arg_num(&args, 2, 1.0)?;
3197 return Ok(Value::Number(x.clamp(lo, hi)));
3198 },
3199
3200 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" => {
3202 return Ok(Value::Number(std::f64::consts::PI))
3203 },
3204 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" => {
3205 return Ok(Value::Number(std::f64::consts::TAU))
3206 },
3207
3208 "vnoise" | "noise2" | "นอยส์2ดี" | "柏林噪声2D" | "バリューノイズ2D" | "값노이즈2D" =>
3214 {
3215 let x = self.arg_num(&args, 0, 0.0)? as f32;
3216 let y = self.arg_num(&args, 1, 0.0)? as f32;
3217 let seed = self.arg_num(&args, 2, 0.0)? as u32;
3218 return Ok(Value::Number(tex_vnoise(x, y, seed) as f64));
3219 },
3220
3221 "fbm" | "นอยส์ออร์แกนิก" | "分形噪声" | "フラクタルノイズ" | "프랙탈노이즈" =>
3222 {
3223 let x = self.arg_num(&args, 0, 0.0)? as f32;
3224 let y = self.arg_num(&args, 1, 0.0)? as f32;
3225 let octaves = self.arg_num(&args, 2, 4.0)? as u32;
3226 let seed = self.arg_num(&args, 3, 0.0)? as u32;
3227 return Ok(Value::Number(tex_fbm(x, y, octaves, seed) as f64));
3228 },
3229
3230 "perlin"
3231 | "perlin3"
3232 | "เพอร์ลิน3ดี"
3233 | "柏林噪声3D"
3234 | "パーリンノイズ3D"
3235 | "펄린노이즈3D" => {
3236 let x = self.arg_num(&args, 0, 0.0)? as f32;
3237 let y = self.arg_num(&args, 1, 0.0)? as f32;
3238 let z = self.arg_num(&args, 2, 0.0)? as f32;
3239 return Ok(Value::Number(perlin3(x, y, z) as f64));
3240 },
3241
3242 "lerp" | "ค่าระหว่าง" | "线性插值" | "線形補間" | "선형보간" =>
3244 {
3245 let a = self.arg_num(&args, 0, 0.0)?;
3246 let b = self.arg_num(&args, 1, 1.0)?;
3247 let t = self.arg_num(&args, 2, 0.0)?;
3248 return Ok(Value::Number(a + (b - a) * t));
3249 },
3250
3251 "smoothstep" | "เปลี่ยนแบบนุ่ม" | "平滑步进" | "スムーズステップ" | "스무스스텝" =>
3252 {
3253 let lo = self.arg_num(&args, 0, 0.0)?;
3254 let hi = self.arg_num(&args, 1, 1.0)?;
3255 let x = self.arg_num(&args, 2, 0.5)?;
3256 let t = ((x - lo) / (hi - lo)).clamp(0.0, 1.0);
3257 return Ok(Value::Number(t * t * (3.0 - 2.0 * t)));
3258 },
3259
3260 "rand" | "สุ่ม" | "随机" | "乱数" | "난수" => {
3261 let val = fast_rand_f64(&mut self.rand_state);
3262 return Ok(Value::Number(val));
3263 },
3264
3265 "sign" | "เครื่องหมาย" | "符号" | "符号関数" | "부호" => {
3266 let x = self.arg_num(&args, 0, 0.0)?;
3267 return Ok(Value::Number(x.signum()));
3268 },
3269
3270 "hsv_to_rgb" | "เอชเอสวีเป็นRGB" | "HSV转RGB" | "HSV変換RGB" | "HSV변환RGB" =>
3271 {
3272 let h = self.arg_num(&args, 0, 0.0)?; let s = self.arg_num(&args, 1, 1.0)?; let v = self.arg_num(&args, 2, 1.0)?; let c = v * s;
3276 let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
3277 let m = v - c;
3278 let (r1, g1, b1) = if h < 60.0 {
3279 (c, x, 0.0)
3280 } else if h < 120.0 {
3281 (x, c, 0.0)
3282 } else if h < 180.0 {
3283 (0.0, c, x)
3284 } else if h < 240.0 {
3285 (0.0, x, c)
3286 } else if h < 300.0 {
3287 (x, 0.0, c)
3288 } else {
3289 (c, 0.0, x)
3290 };
3291 let r = ((r1 + m) * 255.0).round();
3292 let g = ((g1 + m) * 255.0).round();
3293 let b = ((b1 + m) * 255.0).round();
3294 return Ok(Value::List(Rc::new(vec![
3295 Value::Number(r),
3296 Value::Number(g),
3297 Value::Number(b),
3298 ])));
3299 },
3300
3301 "lerp_color" | "ไล่สี" | "颜色插值" | "色補間" | "색보간" => {
3302 let r1 = self.arg_num(&args, 0, 0.0)?;
3303 let g1 = self.arg_num(&args, 1, 0.0)?;
3304 let b1 = self.arg_num(&args, 2, 0.0)?;
3305 let r2 = self.arg_num(&args, 3, 255.0)?;
3306 let g2 = self.arg_num(&args, 4, 255.0)?;
3307 let b2 = self.arg_num(&args, 5, 255.0)?;
3308 let t = self.arg_num(&args, 6, 0.0)?;
3309 let r = r1 + (r2 - r1) * t;
3310 let g = g1 + (g2 - g1) * t;
3311 let b = b1 + (b2 - b1) * t;
3312 let c = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
3313 self.gfx.borrow_mut().color = c;
3314 return Ok(Value::Unit);
3315 },
3316
3317 "time_now" | "เวลาปัจจุบัน" | "当前时间" | "経過時間" | "현재시간" =>
3319 {
3320 return Ok(Value::Number(
3321 crate::runtime::now_secs() - self.start_time_secs,
3322 ));
3323 },
3324
3325 "epoch_now" | "เวลาโลก" | "datetime" | "现在时刻" | "現在時刻" | "현재시각" =>
3329 {
3330 return Ok(Value::Number(crate::runtime::now_secs()));
3331 },
3332
3333 "frame_count" | "เฟรม" | "帧数" | "フレーム数" | "프레임수" => {
3334 return Ok(Value::Number(self.frame_num as f64));
3335 },
3336
3337 "mic_open" | "เปิดไมค์" | "开麦克风" | "マイク開く" | "마이크열기" =>
3339 {
3340 #[cfg(not(target_arch = "wasm32"))]
3341 {
3342 match ling_mic::MicInput::open(Default::default()) {
3343 Ok(mic) => {
3344 let _ = mic.start(|_samples: &[f32]| {}); self.mic = Some(mic);
3346 return Ok(Value::Number(1.0)); },
3348 Err(_e) => {
3351 self.mic = None;
3352 return Ok(Value::Number(0.0));
3353 },
3354 }
3355 }
3356 #[cfg(target_arch = "wasm32")]
3357 return Ok(Value::Unit);
3358 },
3359
3360 "mic_rms" | "เสียงRMS" | "麦克风音量" | "マイクRMS" | "마이크RMS" =>
3361 {
3362 #[cfg(not(target_arch = "wasm32"))]
3363 {
3364 let rms = self
3365 .mic
3366 .as_ref()
3367 .map(|m: &ling_mic::MicInput| m.rms())
3368 .unwrap_or(0.0);
3369 return Ok(Value::Number(rms as f64));
3370 }
3371 #[cfg(target_arch = "wasm32")]
3372 return Ok(Value::Number(0.0));
3373 },
3374
3375 "mic_peak" | "เสียงพีค" | "麦克风峰值" | "マイクピーク" | "마이크피크" =>
3376 {
3377 #[cfg(not(target_arch = "wasm32"))]
3378 {
3379 let peak = self
3380 .mic
3381 .as_ref()
3382 .map(|m: &ling_mic::MicInput| m.peak())
3383 .unwrap_or(0.0);
3384 return Ok(Value::Number(peak as f64));
3385 }
3386 #[cfg(target_arch = "wasm32")]
3387 return Ok(Value::Number(0.0));
3388 },
3389
3390 "mic_fft" | "วิเคราะห์เสียงสด" | "实时频谱" | "リアルタイムFFT" | "실시간FFT" =>
3391 {
3392 #[cfg(not(target_arch = "wasm32"))]
3393 {
3394 let n = self.arg_num(&args, 0, 8.0)? as usize;
3395 if let Some(mic) = self.mic.as_ref() {
3396 let samples = mic.latest_samples();
3397 self.fft.borrow_mut().push_samples(&samples);
3398 }
3399 let bands = self.fft.borrow().freq_bands(n);
3400 let result: Vec<Value> =
3401 bands.iter().map(|&v| Value::Number(v as f64)).collect();
3402 return Ok(Value::List(Rc::new(result)));
3403 }
3404 #[cfg(target_arch = "wasm32")]
3405 return Ok(Value::List(Vec::new().into()));
3406 },
3407
3408 "set_blend" | "โหมดผสม" | "混合模式" | "ブレンドモード" | "블렌드모드" =>
3410 {
3411 let mode = self.arg_num(&args, 0, 0.0)? as u8;
3412 let mut gfx = self.gfx.borrow_mut();
3413 gfx.blend = mode;
3414 let a = gfx.alpha;
3415 gfx.depth_queue.set_state(mode, a); return Ok(Value::Unit);
3417 },
3418
3419 "set_antialias" | "ตั้งลบรอยหยัก" | "抗锯齿" | "アンチエイリアス" | "안티에일리어싱" =>
3423 {
3424 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3425 self.gfx.borrow_mut().antialias = on;
3426 return Ok(Value::Unit);
3427 },
3428 "get_antialias"
3430 | "อ่านลบรอยหยัก"
3431 | "读取抗锯齿"
3432 | "アンチエイリアス取得"
3433 | "안티에일리어싱상태" => {
3434 return Ok(Value::Bool(self.gfx.borrow().antialias));
3435 },
3436
3437 "set_font_antialias" | "글꼴안티에일리어싱" => {
3441 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3442 self.gfx.borrow_mut().font_antialias = on;
3443 return Ok(Value::Unit);
3444 },
3445 "get_font_antialias" | "글꼴안티에일리어싱상태" => {
3447 return Ok(Value::Bool(self.gfx.borrow().font_antialias));
3448 },
3449
3450 "draw_circle" | "วาดวงกลม" | "画圆" | "円描画" | "원그리기" =>
3452 {
3453 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3454 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3455 let r = self.arg_num(&args, 2, 10.0)? as i32;
3456 let mut gfx = self.gfx.borrow_mut();
3457 let (w, h, color, blend) =
3458 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3459 if gfx.antialias {
3460 let (uw, uh) = (gfx.width, gfx.height);
3461 let segs = ((r.max(1) as u32) * 4).clamp(24, 512);
3462 crate::gfx::raster::draw_arc(
3463 &mut gfx.buffer,
3464 uw,
3465 uh,
3466 color,
3467 true,
3468 blend == 1,
3469 cx as f32,
3470 cy as f32,
3471 r as f32,
3472 0.0,
3473 std::f32::consts::TAU,
3474 segs,
3475 );
3476 } else {
3477 draw_circle_outline(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3478 }
3479 return Ok(Value::Unit);
3480 },
3481
3482 "draw_filled_circle"
3483 | "draw_disc"
3484 | "วาดวงกลมทึบ"
3485 | "画实心圆"
3486 | "塗りつぶし円"
3487 | "원채우기" => {
3488 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3489 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3490 let r = self.arg_num(&args, 2, 10.0)? as i32;
3491 let mut gfx = self.gfx.borrow_mut();
3492 let (w, h, color, blend) =
3493 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3494 draw_circle_filled(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3495 return Ok(Value::Unit);
3496 },
3497
3498 "draw_arc" | "arc" | "วาดส่วนโค้ง" | "画弧" | "円弧描画" | "호그리기" =>
3502 {
3503 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3504 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3505 let r = self.arg_num(&args, 2, 10.0)? as f32;
3506 let a0 = self.arg_num(&args, 3, 0.0)? as f32;
3507 let a1 = self.arg_num(&args, 4, std::f64::consts::TAU)? as f32;
3508 let default_segs = ((r.abs() * (a1 - a0).abs()).ceil() as u32).clamp(8, 1024);
3509 let segs = self.arg_num(&args, 5, default_segs as f64)? as u32;
3510 let mut gfx = self.gfx.borrow_mut();
3511 let color = gfx.color;
3512 #[cfg(not(target_arch = "wasm32"))]
3513 {
3514 let (uw, uh, aa, add) = (gfx.width, gfx.height, gfx.antialias, gfx.blend == 1);
3515 crate::gfx::raster::draw_arc(
3516 &mut gfx.buffer,
3517 uw,
3518 uh,
3519 color,
3520 aa,
3521 add,
3522 cx,
3523 cy,
3524 r,
3525 a0,
3526 a1,
3527 segs,
3528 );
3529 }
3530 #[cfg(target_arch = "wasm32")]
3531 {
3532 let segs_f = segs.max(1);
3533 let step = (a1 - a0) / segs_f as f32;
3534 let mut px = cx + r * a0.cos();
3535 let mut py = cy + r * a0.sin();
3536 let mut i = 1u32;
3537 while i <= segs_f {
3538 let a = a0 + step * i as f32;
3539 let nx = cx + r * a.cos();
3540 let ny = cy + r * a.sin();
3541 gfx.depth_queue.push_line(0.0, color, px, py, nx, ny);
3542 px = nx;
3543 py = ny;
3544 i += 1;
3545 }
3546 }
3547 return Ok(Value::Unit);
3548 },
3549
3550 "set_alpha" | "ตั้งความโปร่งใส" | "设透明" | "アルファ設定" | "투명도설정" =>
3556 {
3557 let a = self.arg_num(&args, 0, 1.0)? as f32;
3558 let mut gfx = self.gfx.borrow_mut();
3559 gfx.alpha = a.clamp(0.0, 1.0);
3560 let (m, al) = (gfx.blend, gfx.alpha);
3561 gfx.depth_queue.set_state(m, al); return Ok(Value::Unit);
3563 },
3564
3565 "mesh_hue" | "หมุนสีเมช" =>
3568 {
3569 let h = self.arg_num(&args, 0, 0.0)? as f32;
3570 let g = self.arg_num(&args, 1, 1.0)? as f32;
3571 let mut gfx = self.gfx.borrow_mut();
3572 gfx.mesh_hue = h;
3573 gfx.mesh_hue_gain = g.max(0.0);
3574 return Ok(Value::Unit);
3575 },
3576
3577 "set_frame_blur" | "frame_blur" | "เบลอเฟรม" =>
3580 {
3581 let a = self.arg_num(&args, 0, 0.0)? as f32;
3582 let mut gfx = self.gfx.borrow_mut();
3583 gfx.frame_blur = a.clamp(0.0, 0.95);
3584 if gfx.frame_blur <= 0.0 {
3585 gfx.prev_frame = Vec::new();
3586 }
3587 return Ok(Value::Unit);
3588 },
3589
3590 "set_line_hue_cycle" | "ตั้งวนสีเส้น" => {
3594 let rate = self.arg_num(&args, 0, 0.0)?;
3595 crate::runtime::set_line_hue_rate(rate);
3596 return Ok(Value::Unit);
3597 },
3598
3599 "set_color_space" | "ปริภูมิสี" | "色彩空间" | "色空間" | "색공간" =>
3603 {
3604 let m = self.arg_num(&args, 0, 0.0)? as i64;
3605 self.gfx.borrow_mut().linear_blend = m != 0;
3606 return Ok(Value::Unit);
3607 },
3608
3609 "set_gradient_space" | "ปริภูมิไล่สี" | "渐变空间" | "グラデ空間" | "그라데이션공간" =>
3612 {
3613 let m = self.arg_num(&args, 0, 1.0)? as i64;
3614 self.gfx.borrow_mut().grad_oklab = m != 0;
3615 return Ok(Value::Unit);
3616 },
3617
3618 "mix_color" | "ผสมสี" | "混合颜色" | "色混合" | "색혼합" => {
3622 let c0 = rgb(
3623 self.arg_num(&args, 0, 0.0)?,
3624 self.arg_num(&args, 1, 0.0)?,
3625 self.arg_num(&args, 2, 0.0)?,
3626 );
3627 let c1 = rgb(
3628 self.arg_num(&args, 3, 255.0)?,
3629 self.arg_num(&args, 4, 255.0)?,
3630 self.arg_num(&args, 5, 255.0)?,
3631 );
3632 let t = self.arg_num(&args, 6, 0.5)? as f32;
3633 self.gfx.borrow_mut().color = crate::gfx::color::mix_oklab(c0, c1, t);
3634 return Ok(Value::Unit);
3635 },
3636
3637 "set_depth_test" | "ทดสอบความลึก" | "深度测试" | "深度テスト" | "깊이테스트" =>
3641 {
3642 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3643 self.gfx.borrow_mut().depth_test = on;
3644 return Ok(Value::Unit);
3645 },
3646
3647 "set_flat_shade" | "ตั้งแฟลตเชด" | "平面着色" | "フラット着色" | "평면음영" =>
3650 {
3651 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3652 self.gfx.borrow_mut().flat_shade = on;
3653 return Ok(Value::Unit);
3654 },
3655
3656 "set_normal_override" =>
3659 {
3660 let x = self.arg_num(&args, 0, 0.0)? as f32;
3661 let y = self.arg_num(&args, 1, -1.0)? as f32;
3662 let z = self.arg_num(&args, 2, 0.0)? as f32;
3663 self.gfx.borrow_mut().normal_override = Some([x, y, z]);
3664 return Ok(Value::Unit);
3665 },
3666
3667 "reset_normal_override" =>
3668 {
3669 self.gfx.borrow_mut().normal_override = None;
3670 return Ok(Value::Unit);
3671 },
3672
3673 "clear_depth" | "ล้างความลึก" | "清深度" | "深度クリア" | "깊이지우기" =>
3677 {
3678 self.gfx.borrow_mut().zbuf_needs_clear = true;
3679 return Ok(Value::Unit);
3680 },
3681
3682 "depth_blur" | "เบลอความลึก" | "dof" | "depth_of_field" | "景深" =>
3688 {
3689 let focus = self.arg_num(&args, 0, 30.0)? as f32;
3690 let range = self.arg_num(&args, 1, 60.0)? as f32;
3691 let radius = self.arg_num(&args, 2, 3.0)?.max(0.0) as usize;
3692 let oil = self.arg_num(&args, 3, 0.0)? as f32;
3695 let mut gfx = self.gfx.borrow_mut();
3696 let w = gfx.width;
3697 let h = gfx.height;
3698 if gfx.depth_buf.len() == w * h {
3699 let g = &mut *gfx;
3700 crate::gfx::raster::depth_of_field(
3701 &mut g.buffer,
3702 &g.depth_buf,
3703 w,
3704 h,
3705 focus,
3706 range,
3707 radius,
3708 oil,
3709 );
3710 }
3711 return Ok(Value::Unit);
3712 },
3713
3714 "light_pool" | "แอ่งแสง" | "光池" | "ライトプール" | "빛웅덩이" =>
3720 {
3721 let x = self.arg_num(&args, 0, 0.0)? as f32;
3722 let y = self.arg_num(&args, 1, 0.0)? as f32;
3723 let z = self.arg_num(&args, 2, 0.0)? as f32;
3724 let radius = self.arg_num(&args, 3, 20.0)? as f32;
3725 let r = self.arg_num(&args, 4, 255.0)? as f32 / 255.0;
3726 let g = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3727 let b = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3728 let inten = self.arg_num(&args, 7, 1.0)? as f32;
3729 self.gfx
3730 .borrow_mut()
3731 .emit_light_pool(x, y, z, radius, [r, g, b], inten);
3732 return Ok(Value::Unit);
3733 },
3734
3735 "light_beam" | "ลำแสงไฟ" | "光柱" | "ライトビーム" | "빛기둥" =>
3740 {
3741 let x = self.arg_num(&args, 0, 0.0)? as f32;
3742 let y = self.arg_num(&args, 1, 0.0)? as f32;
3743 let z = self.arg_num(&args, 2, 0.0)? as f32;
3744 let fy = self.arg_num(&args, 3, 0.0)? as f32;
3745 let radius = self.arg_num(&args, 4, 14.0)? as f32;
3746 let r = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3747 let g = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3748 let b = self.arg_num(&args, 7, 255.0)? as f32 / 255.0;
3749 let inten = self.arg_num(&args, 8, 1.0)? as f32;
3750 self.gfx
3751 .borrow_mut()
3752 .emit_light_beam(x, y, z, fy, radius, [r, g, b], inten);
3753 return Ok(Value::Unit);
3754 },
3755
3756 "grad_triangle" | "สามเหลี่ยมไล่สี" | "渐变三角" | "グラデ三角" | "그라데삼각" =>
3760 {
3761 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3762 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3763 let c0 = rgb(
3764 self.arg_num(&args, 2, 255.0)?,
3765 self.arg_num(&args, 3, 255.0)?,
3766 self.arg_num(&args, 4, 255.0)?,
3767 );
3768 let x1 = self.arg_num(&args, 5, 0.0)? as f32;
3769 let y1 = self.arg_num(&args, 6, 0.0)? as f32;
3770 let c1 = rgb(
3771 self.arg_num(&args, 7, 255.0)?,
3772 self.arg_num(&args, 8, 255.0)?,
3773 self.arg_num(&args, 9, 255.0)?,
3774 );
3775 let x2 = self.arg_num(&args, 10, 0.0)? as f32;
3776 let y2 = self.arg_num(&args, 11, 0.0)? as f32;
3777 let c2 = rgb(
3778 self.arg_num(&args, 12, 255.0)?,
3779 self.arg_num(&args, 13, 255.0)?,
3780 self.arg_num(&args, 14, 255.0)?,
3781 );
3782 let mut gfx = self.gfx.borrow_mut();
3783 let (w, h, alpha, mode, lin, ok) = (
3784 gfx.width,
3785 gfx.height,
3786 gfx.alpha,
3787 gfx.blend,
3788 gfx.linear_blend,
3789 gfx.grad_oklab,
3790 );
3791 crate::gfx::raster::fill_triangle_grad(
3792 &mut gfx.buffer,
3793 w,
3794 h,
3795 alpha,
3796 mode,
3797 lin,
3798 ok,
3799 x0,
3800 y0,
3801 c0,
3802 x1,
3803 y1,
3804 c1,
3805 x2,
3806 y2,
3807 c2,
3808 );
3809 return Ok(Value::Unit);
3810 },
3811
3812 "grad_rect" | "สี่เหลี่ยมไล่สี" | "渐变矩形" | "グラデ矩形" | "그라데사각" =>
3815 {
3816 let x = self.arg_num(&args, 0, 0.0)? as f32;
3817 let y = self.arg_num(&args, 1, 0.0)? as f32;
3818 let rw = self.arg_num(&args, 2, 0.0)? as f32;
3819 let rh = self.arg_num(&args, 3, 0.0)? as f32;
3820 let c0 = rgb(
3821 self.arg_num(&args, 4, 255.0)?,
3822 self.arg_num(&args, 5, 255.0)?,
3823 self.arg_num(&args, 6, 255.0)?,
3824 );
3825 let c1 = rgb(
3826 self.arg_num(&args, 7, 0.0)?,
3827 self.arg_num(&args, 8, 0.0)?,
3828 self.arg_num(&args, 9, 0.0)?,
3829 );
3830 let dir = self.arg_num(&args, 10, 1.0)? as u8;
3831 let mut gfx = self.gfx.borrow_mut();
3832 let (w, h, alpha, mode, lin, ok) = (
3833 gfx.width,
3834 gfx.height,
3835 gfx.alpha,
3836 gfx.blend,
3837 gfx.linear_blend,
3838 gfx.grad_oklab,
3839 );
3840 crate::gfx::raster::fill_rect_grad(
3841 &mut gfx.buffer,
3842 w,
3843 h,
3844 alpha,
3845 mode,
3846 lin,
3847 ok,
3848 x,
3849 y,
3850 rw,
3851 rh,
3852 c0,
3853 c1,
3854 dir,
3855 );
3856 return Ok(Value::Unit);
3857 },
3858
3859 "shadow_blob" | "เงาวงรี" | "阴影斑" | "影ブロブ" | "그림자블롭" =>
3863 {
3864 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3865 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3866 let rx = self.arg_num(&args, 2, 16.0)? as f32;
3867 let ry = self.arg_num(&args, 3, 8.0)? as f32;
3868 let a = self.arg_num(&args, 4, 0.5)? as f32;
3869 let mut gfx = self.gfx.borrow_mut();
3870 let (w, h, color, soft, mode, lin) = (
3871 gfx.width,
3872 gfx.height,
3873 gfx.color,
3874 gfx.shadow.soft,
3875 gfx.blend,
3876 gfx.linear_blend,
3877 );
3878 crate::gfx::raster::fill_disc_soft(
3879 &mut gfx.buffer,
3880 w,
3881 h,
3882 cx,
3883 cy,
3884 rx,
3885 ry,
3886 color,
3887 a,
3888 soft,
3889 mode,
3890 lin,
3891 );
3892 return Ok(Value::Unit);
3893 },
3894
3895 "cast_shadow" | "ทอดเงา" | "投射阴影" | "影を落とす" | "그림자드리우기" =>
3900 {
3901 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3902 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3903 let height = (self.arg_num(&args, 2, 0.0)? as f32).max(0.0);
3904 let mut gfx = self.gfx.borrow_mut();
3905 let sp = gfx.shadow;
3906 let radius = (sp.base + sp.grow * height).max(0.5);
3907 let alpha = (sp.alpha - sp.fade * height).clamp(0.04, 1.0);
3908 let soft = (sp.soft + height * 0.004).clamp(0.0, 0.95);
3909 let (w, h, color, mode, lin) = (
3910 gfx.width,
3911 gfx.height,
3912 gfx.color,
3913 gfx.blend,
3914 gfx.linear_blend,
3915 );
3916 crate::gfx::raster::fill_disc_soft(
3917 &mut gfx.buffer,
3918 w,
3919 h,
3920 cx,
3921 cy,
3922 radius,
3923 radius * 0.62,
3924 color,
3925 alpha,
3926 soft,
3927 mode,
3928 lin,
3929 );
3930 return Ok(Value::Unit);
3931 },
3932
3933 "shadow_params" | "ตั้งค่าเงา" | "阴影参数" | "影設定" | "그림자설정" =>
3936 {
3937 let cur = self.gfx.borrow().shadow;
3938 let base = self.arg_num(&args, 0, cur.base as f64)? as f32;
3939 let grow = self.arg_num(&args, 1, cur.grow as f64)? as f32;
3940 let alpha = self.arg_num(&args, 2, cur.alpha as f64)? as f32;
3941 let fade = self.arg_num(&args, 3, cur.fade as f64)? as f32;
3942 let soft = self.arg_num(&args, 4, cur.soft as f64)? as f32;
3943 self.gfx.borrow_mut().shadow =
3944 crate::gfx::ShadowParams { base, grow, alpha, fade, soft };
3945 return Ok(Value::Unit);
3946 },
3947
3948 "depth_triangle" | "สามเหลี่ยมเรียงลึก" | "深度三角" | "深度三角形" | "깊이삼각" =>
3953 {
3954 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3955 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3956 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3957 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3958 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
3959 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
3960 let z = self.arg_num(&args, 6, 0.0)? as f32;
3961 let mut gfx = self.gfx.borrow_mut();
3962 let color = gfx.color;
3963 gfx.depth_queue
3964 .push_triangle(z, color, x0, y0, x1, y1, x2, y2);
3965 return Ok(Value::Unit);
3966 },
3967
3968 "depth_line" | "เส้นเรียงลึก" | "深度线" | "深度線" | "깊이선" =>
3971 {
3972 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3973 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3974 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3975 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3976 let z = self.arg_num(&args, 4, 0.0)? as f32;
3977 let mut gfx = self.gfx.borrow_mut();
3978 let color = gfx.color;
3979 gfx.depth_queue.push_line(z, color, x0, y0, x1, y1);
3980 return Ok(Value::Unit);
3981 },
3982
3983 "เปิดหน้าต่าง" | "open_window" | "gfx_window" | "开窗" | "ウィンドウ開く" | "창열기" =>
3990 {
3991 let w = self.arg_num(&args, 0, 800.0)? as usize;
3992 let h = self.arg_num(&args, 1, 600.0)? as usize;
3993 #[cfg(not(target_arch = "wasm32"))]
3994 {
3995 let title = args
3996 .get(2)
3997 .map(|v| v.to_string())
3998 .unwrap_or_else(|| "Ling".into());
3999 let mut gfx = self.gfx.borrow_mut();
4000 let mut win = minifb::Window::new(
4001 &title,
4002 w,
4003 h,
4004 minifb::WindowOptions {
4005 resize: false,
4006 scale: minifb::Scale::X1,
4007 ..Default::default()
4008 },
4009 )
4010 .map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
4011 apply_frame_pacing(&mut win, gfx.vsync);
4012 gfx.buffer = vec![0u32; w * h];
4013 gfx.width = w;
4014 gfx.height = h;
4015 gfx.window = Some(win);
4016 gfx.topmost_window = false;
4017 gfx.sync_projection();
4018 hide_console_window();
4019 }
4020 #[cfg(target_arch = "wasm32")]
4021 {
4022 let mut gfx = self.gfx.borrow_mut();
4023 gfx.width = w;
4024 gfx.height = h;
4025 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4027 crate::gfx::webgl::resize(w as u32, h as u32);
4028 }
4029 return Ok(Value::Unit);
4030 },
4031
4032 "เติม" | "fill" | "gfx_fill" | "clear" | "填" | "塗り潰し" | "채우기" | "清"
4034 | "消去" | "지우기" => {
4035 let r = self.arg_num(&args, 0, 0.0)? as u32;
4036 let g = self.arg_num(&args, 1, 0.0)? as u32;
4037 let b = self.arg_num(&args, 2, 0.0)? as u32;
4038 #[cfg(not(target_arch = "wasm32"))]
4039 {
4040 let c = (r << 16) | (g << 8) | b;
4041 let mut gfx = self.gfx.borrow_mut();
4042 gfx.buffer.fill(c);
4043 gfx.zbuf_needs_clear = true; gfx.edge_set.clear(); }
4046 #[cfg(target_arch = "wasm32")]
4047 {
4048 let mut gfx = self.gfx.borrow_mut();
4049 gfx.fill_r = r as f32 / 255.0;
4050 gfx.fill_g = g as f32 / 255.0;
4051 gfx.fill_b = b as f32 / 255.0;
4052 let c = (r << 16) | (g << 8) | b;
4053 gfx.buffer.fill(c);
4054 gfx.zbuf_needs_clear = true;
4055 gfx.edge_set.clear();
4056 }
4057 return Ok(Value::Unit);
4058 },
4059
4060 "set_color_hsl" | "颜色HSL" | "色相" | "HSL色" | "HSL색설정" | "สีHSLวาด" =>
4063 {
4064 let h = self.arg_num(&args, 0, 0.0)?;
4065 let s = self.arg_num(&args, 1, 70.0)?;
4066 let l = self.arg_num(&args, 2, 50.0)?;
4067 let hex = hsl_to_hex(h, s, l);
4068 let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
4069 let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
4070 let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
4071 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4072 return Ok(Value::Unit);
4073 },
4074
4075 "สีดินสอ" | "set_color" | "gfx_color" | "color" | "设色" | "色設定" | "색설정" =>
4077 {
4078 let r = self.arg_num(&args, 0, 255.0)? as u32;
4079 let g = self.arg_num(&args, 1, 255.0)? as u32;
4080 let b = self.arg_num(&args, 2, 255.0)? as u32;
4081 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4082 return Ok(Value::Unit);
4083 },
4084
4085 "วาดสามเหลี่ยม"
4087 | "draw_triangle"
4088 | "gfx_triangle"
4089 | "triangle"
4090 | "画三角"
4091 | "三角形描画"
4092 | "삼각형그리기" => {
4093 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4094 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4095 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4096 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4097 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
4098 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
4099 let mut gfx = self.gfx.borrow_mut();
4100 let color = gfx.color;
4101 #[cfg(not(target_arch = "wasm32"))]
4102 {
4103 let w = gfx.width;
4104 let h = gfx.height;
4105 fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
4106 }
4107 #[cfg(target_arch = "wasm32")]
4108 gfx.depth_queue
4109 .push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
4110 return Ok(Value::Unit);
4111 },
4112
4113 "วาดเส้น" | "draw_line" | "gfx_line" | "line" | "画线" | "線描く" | "선그리기" =>
4115 {
4116 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4117 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4118 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4119 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4120 let mut gfx = self.gfx.borrow_mut();
4121 let color = gfx.color;
4122 #[cfg(not(target_arch = "wasm32"))]
4123 {
4124 let w = gfx.width;
4125 let h = gfx.height;
4126 let aa = gfx.antialias;
4127 let add = gfx.blend == 1;
4128 if aa {
4129 crate::gfx::raster::draw_line_aa(
4130 &mut gfx.buffer,
4131 w,
4132 h,
4133 color,
4134 add,
4135 x0,
4136 y0,
4137 x1,
4138 y1,
4139 );
4140 } else {
4141 draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
4142 }
4143 }
4144 #[cfg(target_arch = "wasm32")]
4145 gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
4146 return Ok(Value::Unit);
4147 },
4148
4149 "วาดจุด" | "draw_pixel" | "gfx_pixel" | "pixel" | "画点" | "点描く" | "점그리기" =>
4151 {
4152 let px = self.arg_num(&args, 0, 0.0)? as i32;
4153 let py = self.arg_num(&args, 1, 0.0)? as i32;
4154 #[cfg(not(target_arch = "wasm32"))]
4155 {
4156 let mut gfx = self.gfx.borrow_mut();
4157 let color = gfx.color;
4158 let w = gfx.width;
4159 let h = gfx.height;
4160 if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
4161 gfx.buffer[py as usize * w + px as usize] = color;
4162 }
4163 }
4164 #[cfg(target_arch = "wasm32")]
4165 {
4166 let mut gfx = self.gfx.borrow_mut();
4168 let color = gfx.color;
4169 let x = px as f32;
4170 let y = py as f32;
4171 gfx.depth_queue
4172 .push_triangle(0.0, color, x, y, x + 1.0, y, x + 1.0, y + 1.0);
4173 gfx.depth_queue
4174 .push_triangle(0.0, color, x, y, x + 1.0, y + 1.0, x, y + 1.0);
4175 }
4176 return Ok(Value::Unit);
4177 },
4178
4179 "แสดงผล" | "present" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시" =>
4181 {
4182 #[cfg(not(target_arch = "wasm32"))]
4195 let pre_update_mouse_down = self
4196 .gfx
4197 .borrow()
4198 .window
4199 .as_ref()
4200 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
4201 .unwrap_or(false);
4202 #[cfg(not(target_arch = "wasm32"))]
4203 {
4204 ling_fps_tick();
4205 ling_phase_frame();
4206 {
4208 let mut gfx = self.gfx.borrow_mut();
4209 if !gfx.depth_queue.is_empty() {
4210 let w = gfx.width;
4211 let h = gfx.height;
4212 let dt = gfx.depth_test;
4213 let reset_z = gfx.zbuf_needs_clear;
4214 let (bm, ba) = (gfx.blend, gfx.alpha);
4215 let aa = gfx.antialias;
4216 let queue = std::mem::take(&mut gfx.depth_queue);
4217 {
4218 let g = &mut *gfx;
4219 let z = if dt { Some(&mut g.depth_buf) } else { None };
4220 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4221 }
4222 gfx.depth_queue.set_state(bm, ba);
4223 gfx.zbuf_needs_clear = false;
4224 }
4225 let _t = std::time::Instant::now();
4226 if !gfx.post_done {
4227 gfx.toon_post_process();
4228 }
4229 gfx.post_done = false;
4230 ling_phase_add(phase::TOON, _t.elapsed().as_nanos());
4231 let w = gfx.width;
4232 let h = gfx.height;
4233 let g = &mut *gfx;
4234 if g.frame_blur > 0.0 {
4235 let a = (g.frame_blur.clamp(0.0, 0.995) * 256.0) as u32;
4240 if g.prev_frame.len() != g.buffer.len() {
4241 g.prev_frame = g.buffer.clone();
4242 }
4243 for (dst, prev) in g.buffer.iter_mut().zip(g.prev_frame.iter_mut()) {
4244 let c = *dst;
4245 let pv = *prev;
4246 let pr = (((pv >> 16) & 0xFF) * a) >> 8;
4247 let pg = (((pv >> 8) & 0xFF) * a) >> 8;
4248 let pb = ((pv & 0xFF) * a) >> 8;
4249 let cr = (c >> 16) & 0xFF;
4250 let cg = (c >> 8) & 0xFF;
4251 let cb = c & 0xFF;
4252 let outp = (cr.max(pr) << 16) | (cg.max(pg) << 8) | cb.max(pb);
4253 *dst = outp;
4254 *prev = outp;
4255 }
4256 }
4257 if let Some(win) = g.window.as_mut() {
4258 let _b = std::time::Instant::now();
4259 win.update_with_buffer(&g.buffer, w, h)
4260 .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
4261 ling_phase_add(phase::BLIT, _b.elapsed().as_nanos());
4262 }
4263 }
4264 let mouse_pos = {
4266 let gfx = self.gfx.borrow();
4267 gfx.window
4268 .as_ref()
4269 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
4270 };
4271 let mut gfx = self.gfx.borrow_mut();
4272 if gfx.mouse_captured {
4273 let w = gfx.width as f32;
4274 let h = gfx.height as f32;
4275 if let Some((mx, my)) = mouse_pos {
4276 if gfx.last_mx.is_nan() {
4277 gfx.mouse_dx = 0.0;
4278 gfx.mouse_dy = 0.0;
4279 gfx.last_mx = mx;
4280 gfx.last_my = my;
4281 } else {
4282 gfx.mouse_dx = mx - gfx.last_mx;
4283 gfx.mouse_dy = my - gfx.last_my;
4284 let margin = 6.0;
4287 let (mut nx, mut ny, mut warp) = (mx, my, false);
4288 if mx < margin {
4289 nx = w - margin - 2.0;
4290 warp = true;
4291 } else if mx > w - margin {
4292 nx = margin + 2.0;
4293 warp = true;
4294 }
4295 if my < margin {
4296 ny = h - margin - 2.0;
4297 warp = true;
4298 } else if my > h - margin {
4299 ny = margin + 2.0;
4300 warp = true;
4301 }
4302 if warp {
4303 #[cfg(windows)]
4304 unsafe {
4305 #[repr(C)]
4306 struct RECT {
4307 left: i32,
4308 top: i32,
4309 right: i32,
4310 bottom: i32,
4311 }
4312 extern "system" {
4313 fn GetForegroundWindow() -> isize;
4314 fn GetWindowRect(hwnd: isize, lpRect: *mut RECT)
4315 -> i32;
4316 fn SetCursorPos(x: i32, y: i32) -> i32;
4317 }
4318 let hwnd = GetForegroundWindow();
4319 let mut rect =
4320 RECT { left: 0, top: 0, right: 0, bottom: 0 };
4321 if GetWindowRect(hwnd, &mut rect) != 0 {
4322 SetCursorPos(
4323 rect.left + nx as i32,
4324 rect.top + ny as i32,
4325 );
4326 }
4327 }
4328 gfx.last_mx = nx;
4329 gfx.last_my = ny;
4330 } else {
4331 gfx.last_mx = mx;
4332 gfx.last_my = my;
4333 }
4334 }
4335 } else {
4336 gfx.mouse_dx = 0.0;
4337 gfx.mouse_dy = 0.0;
4338 }
4339 } else if let Some((mx, my)) = mouse_pos {
4340 if gfx.last_mx.is_nan() {
4341 gfx.mouse_dx = 0.0;
4342 gfx.mouse_dy = 0.0;
4343 } else {
4344 gfx.mouse_dx = mx - gfx.last_mx;
4345 gfx.mouse_dy = my - gfx.last_my;
4346 }
4347 gfx.last_mx = mx;
4348 gfx.last_my = my;
4349 } else {
4350 gfx.mouse_dx = 0.0;
4351 gfx.mouse_dy = 0.0;
4352 }
4353
4354 let is_active = gfx.window.as_mut().map(|w| w.is_active()).unwrap_or(true);
4362 if is_active && !gfx.was_active {
4363 gfx.focus_grace_frames = 5;
4364 #[cfg(windows)]
4367 if gfx.topmost_window {
4368 if let Some(w) = gfx.window.as_ref() {
4369 set_window_topmost(w.get_window_handle() as isize, true);
4370 }
4371 }
4372 } else if !is_active && gfx.was_active {
4373 #[cfg(windows)]
4379 if gfx.topmost_window {
4380 if let Some(w) = gfx.window.as_ref() {
4381 set_window_topmost(w.get_window_handle() as isize, false);
4382 }
4383 }
4384 }
4385 gfx.was_active = is_active;
4386 if gfx.focus_grace_frames > 0 {
4387 gfx.focus_grace_frames -= 1;
4388 }
4389 }
4390 #[cfg(target_arch = "wasm32")]
4391 {
4392 {
4393 let mut gfx = self.gfx.borrow_mut();
4397 let w = gfx.width;
4398 let h = gfx.height;
4399 if gfx.buffer.len() != w * h {
4400 gfx.buffer.resize(w * h, 0);
4401 }
4402 if !gfx.depth_queue.is_empty() {
4403 let dt = gfx.depth_test;
4404 let reset_z = gfx.zbuf_needs_clear;
4405 let aa = gfx.antialias;
4406 let queue = std::mem::take(&mut gfx.depth_queue);
4407 {
4408 let g = &mut *gfx;
4409 let z = if dt { Some(&mut g.depth_buf) } else { None };
4410 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4411 }
4412 gfx.zbuf_needs_clear = false;
4413 }
4414 if !gfx.post_done {
4415 gfx.toon_post_process();
4416 }
4417 gfx.post_done = false;
4418 crate::gfx::webgl::blit_rgb(&gfx.buffer, w, h);
4419 }
4420 self.wasm_pace_frame();
4421 }
4422 #[cfg(not(target_arch = "wasm32"))]
4426 {
4427 self.mouse_was_down = pre_update_mouse_down;
4428 }
4429 self.frame_num += 1;
4431 return Ok(Value::Unit);
4432 },
4433
4434 "เปิดหน้าต่างเต็มจอ"
4436 | "open_fullscreen"
4437 | "fullscreen"
4438 | "全屏"
4439 | "全画面"
4440 | "전체화면" => {
4441 #[cfg(target_arch = "wasm32")]
4444 let (default_w, default_h) = {
4445 let (cw, ch) = crate::gfx::webgl::canvas_size();
4446 (cw as f64, ch as f64)
4447 };
4448 #[cfg(all(not(target_arch = "wasm32"), windows))]
4450 let (default_w, default_h) = unsafe {
4451 extern "system" {
4452 fn GetSystemMetrics(nIndex: i32) -> i32;
4453 }
4454 (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
4455 };
4456 #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
4457 let (default_w, default_h) = native_screen_size();
4458
4459 let w = args
4460 .get(1)
4461 .map(|v| self.to_number(v).unwrap_or(default_w) as usize)
4462 .unwrap_or(default_w as usize);
4463 let h = args
4464 .get(2)
4465 .map(|v| self.to_number(v).unwrap_or(default_h) as usize)
4466 .unwrap_or(default_h as usize);
4467 #[cfg(not(target_arch = "wasm32"))]
4468 {
4469 let title = args
4470 .first()
4471 .map(|v| v.to_string())
4472 .unwrap_or_else(|| "Ling".into());
4473 let mut gfx = self.gfx.borrow_mut();
4474 let mut win = minifb::Window::new(
4475 &title,
4476 w,
4477 h,
4478 minifb::WindowOptions {
4479 borderless: true,
4480 title: false,
4481 resize: false,
4482 topmost: true,
4483 scale: minifb::Scale::X1,
4484 ..Default::default()
4485 },
4486 )
4487 .map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
4488 apply_frame_pacing(&mut win, gfx.vsync);
4489 #[cfg(windows)]
4491 let hwnd = win.get_window_handle() as isize;
4492 gfx.buffer = vec![0u32; w * h];
4493 gfx.width = w;
4494 gfx.height = h;
4495 gfx.window = Some(win);
4496 gfx.topmost_window = true;
4497 #[cfg(windows)]
4498 {
4499 gfx.hwnd = hwnd;
4500 }
4501 gfx.sync_projection();
4502 #[cfg(windows)]
4504 make_borderless_fullscreen(hwnd, w as i32, h as i32);
4505 hide_console_window();
4506 #[cfg(windows)]
4513 force_window_focus(hwnd);
4514 }
4515 #[cfg(target_arch = "wasm32")]
4516 {
4517 let mut gfx = self.gfx.borrow_mut();
4518 gfx.width = w;
4519 gfx.height = h;
4520 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4522 crate::gfx::webgl::resize(w as u32, h as u32);
4523 }
4524 return Ok(Value::Unit);
4525 },
4526
4527 "get_width" | "ความกว้าง" | "宽" | "幅取得" | "너비" => {
4529 return Ok(Value::Number(self.gfx.borrow().width as f64));
4530 },
4531 "get_height" | "ความสูง" | "高" | "高取得" | "높이" => {
4532 return Ok(Value::Number(self.gfx.borrow().height as f64));
4533 },
4534
4535 "monitor_width" | "screen_width" | "屏宽" | "画面幅" | "화면너비" | "ความกว้างจอ" =>
4538 {
4539 return Ok(Value::Number(monitor_info().0 as f64));
4540 },
4541 "monitor_height" | "screen_height" | "屏高" | "画面高" | "화면높이" | "ความสูงจอ" =>
4543 {
4544 return Ok(Value::Number(monitor_info().1 as f64));
4545 },
4546 "monitor_refresh"
4548 | "monitor_hz"
4549 | "monitor_fps"
4550 | "refresh_rate"
4551 | "刷新率"
4552 | "リフレッシュレート"
4553 | "주사율"
4554 | "อัตรารีเฟรช" => {
4555 return Ok(Value::Number(monitor_info().2 as f64));
4556 },
4557 "monitor_info" | "screen_info" | "屏幕信息" | "画面情報" | "화면정보" | "ข้อมูลจอ" =>
4559 {
4560 let (w, h, hz) = monitor_info();
4561 return Ok(Value::List(Rc::new(vec![
4562 Value::Number(w as f64),
4563 Value::Number(h as f64),
4564 Value::Number(hz as f64),
4565 ])));
4566 },
4567 "set_fps"
4569 | "set_target_fps"
4570 | "target_fps"
4571 | "设帧率"
4572 | "フレームレート設定"
4573 | "프레임설정"
4574 | "ตั้งเฟรมเรต" => {
4575 #[cfg(not(target_arch = "wasm32"))]
4576 {
4577 let fps = self.arg_num(&args, 0, 60.0)?.max(1.0) as usize;
4578 let mut gfx = self.gfx.borrow_mut();
4579 if let Some(win) = gfx.window.as_mut() {
4580 win.set_target_fps(fps);
4581 }
4582 }
4583 #[cfg(target_arch = "wasm32")]
4584 {
4585 self.wasm_target_fps = self.arg_num(&args, 0, 60.0)?.max(1.0);
4586 self.wasm_next_present_ms = 0.0;
4587 }
4588 return Ok(Value::Unit);
4589 },
4590
4591 "set_vsync" | "vsync" | "垂直同步" | "垂直同期" | "수직동기" | "ตั้งวีซิงก์" =>
4595 {
4596 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4597 #[cfg(not(target_arch = "wasm32"))]
4598 {
4599 let mut gfx = self.gfx.borrow_mut();
4600 gfx.vsync = on;
4601 if let Some(win) = gfx.window.as_mut() {
4602 apply_frame_pacing(win, on);
4603 }
4604 }
4605 #[cfg(target_arch = "wasm32")]
4606 {
4607 self.wasm_target_fps = if on { monitor_info().2 as f64 } else { 240.0 };
4608 self.wasm_next_present_ms = 0.0;
4609 }
4610 return Ok(Value::Unit);
4611 },
4612
4613 "หน้าต่างเปิดอยู่"
4615 | "window_is_open"
4616 | "gfx_is_open"
4617 | "is_open"
4618 | "窗开"
4619 | "開いている"
4620 | "창열림" => {
4621 #[cfg(not(target_arch = "wasm32"))]
4622 {
4623 let gfx = self.gfx.borrow();
4624 if gfx.want_quit {
4625 return Ok(Value::Bool(false));
4626 }
4627 #[cfg(windows)]
4633 let escape_down = if gfx.topmost_window {
4634 window_is_foreground(gfx.hwnd) && os_key_down(0x1B) } else {
4636 gfx.window
4637 .as_ref()
4638 .map(|w| w.is_key_down(minifb::Key::Escape))
4639 .unwrap_or(false)
4640 };
4641 #[cfg(not(windows))]
4642 let escape_down = gfx
4643 .window
4644 .as_ref()
4645 .map(|w| w.is_key_down(minifb::Key::Escape))
4646 .unwrap_or(false);
4647 let open = gfx.window.as_ref().map(|w| w.is_open()).unwrap_or(false)
4648 && !escape_down;
4649 return Ok(Value::Bool(open));
4650 }
4651 #[cfg(target_arch = "wasm32")]
4652 return Ok(Value::Bool(true));
4653 },
4654
4655 "quit" | "exit_game" | "close_window" => {
4658 #[cfg(not(target_arch = "wasm32"))]
4659 {
4660 self.gfx.borrow_mut().want_quit = true;
4661 }
4662 return Ok(Value::Unit);
4663 },
4664
4665 "key_down" | "กดค้าง" | "按键" | "キー押す" | "키누름" => {
4667 #[cfg(not(target_arch = "wasm32"))]
4668 {
4669 let name = self.arg_str(&args, 0, "");
4670 let mut gfx = self.gfx.borrow_mut();
4671 #[cfg(windows)]
4679 if gfx.topmost_window {
4680 if !window_is_foreground(gfx.hwnd) {
4681 return Ok(Value::Bool(false));
4682 }
4683 return Ok(Value::Bool(
4684 str_to_vk(&name).map(os_key_down).unwrap_or(false),
4685 ));
4686 }
4687 if gfx.input_suppressed() {
4688 return Ok(Value::Bool(false));
4689 }
4690 let down = gfx
4691 .window
4692 .as_ref()
4693 .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
4694 .unwrap_or(false);
4695 return Ok(Value::Bool(down));
4696 }
4697 #[cfg(target_arch = "wasm32")]
4698 {
4699 let name = self.arg_str(&args, 0, "");
4700 return Ok(Value::Bool(crate::gfx::wasm_is_key_down(&name)));
4701 }
4702 },
4703
4704 "key_pressed" | "กดปุ่ม" | "键按" | "キー押した" | "키눌림" => {
4706 #[cfg(not(target_arch = "wasm32"))]
4707 {
4708 let name = self.arg_str(&args, 0, "");
4709 let pressed = {
4710 let mut gfx = self.gfx.borrow_mut();
4711 #[cfg(windows)]
4712 let topmost = gfx.topmost_window;
4713 #[cfg(not(windows))]
4714 let topmost = false;
4715 if topmost {
4716 #[cfg(windows)]
4717 {
4718 if !window_is_foreground(gfx.hwnd) {
4719 false
4720 } else {
4721 match str_to_vk(&name) {
4722 Some(vk) => {
4723 let idx = (vk as usize) & 0xFF;
4724 let down = os_key_down(vk);
4725 let was = gfx.raw_keys_prev[idx];
4726 gfx.raw_keys_prev[idx] = down;
4727 down && !was
4728 },
4729 None => false,
4730 }
4731 }
4732 }
4733 #[cfg(not(windows))]
4734 {
4735 false
4736 }
4737 } else if gfx.input_suppressed() {
4738 false
4739 } else {
4740 gfx.window
4741 .as_ref()
4742 .and_then(|w| {
4743 str_to_minifb_key(&name)
4744 .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No))
4745 })
4746 .unwrap_or(false)
4747 }
4748 };
4749 let pressed =
4751 pressed || ((name == "enter" || name == "return") && gamepad::start_edge());
4752 return Ok(Value::Bool(pressed));
4753 }
4754 #[cfg(target_arch = "wasm32")]
4755 {
4756 let name = self.arg_str(&args, 0, "");
4757 let pressed = crate::gfx::wasm_is_key_pressed(&name);
4758 return Ok(Value::Bool(pressed));
4759 }
4760 },
4761
4762 "mouse_dx" | "เมาส์X" | "鼠ΔX" | "マウスΔX" | "마우스ΔX" => {
4764 #[cfg(not(target_arch = "wasm32"))]
4765 return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
4766 #[cfg(target_arch = "wasm32")]
4767 return Ok(Value::Number(crate::gfx::wasm_mouse_dx() as f64));
4768 },
4769 #[cfg(not(target_arch = "wasm32"))]
4771 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" =>
4772 {
4773 let gfx = self.gfx.borrow();
4774 let s = gfx
4775 .window
4776 .as_ref()
4777 .and_then(|w| w.get_scroll_wheel())
4778 .map(|(_, y)| y as f64)
4779 .unwrap_or(0.0);
4780 return Ok(Value::Number(s));
4781 },
4782 #[cfg(target_arch = "wasm32")]
4783 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" =>
4784 {
4785 return Ok(Value::Number(0.0));
4786 },
4787 "mouse_dy" | "เมาส์Y" | "鼠ΔY" | "マウスΔY" | "마우스ΔY" => {
4788 #[cfg(not(target_arch = "wasm32"))]
4789 return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
4790 #[cfg(target_arch = "wasm32")]
4791 return Ok(Value::Number(crate::gfx::wasm_mouse_dy() as f64));
4792 },
4793
4794 "pad_poll" | "手柄轮询" | "パッド更新" | "패드폴링" | "อัปเดตแพด" =>
4797 {
4798 #[cfg(not(target_arch = "wasm32"))]
4799 return Ok(Value::Number(self.pad_poll() as f64));
4800 #[cfg(target_arch = "wasm32")]
4801 return Ok(Value::Number(input_web::poll() as f64));
4802 },
4803 "pad_count" | "手柄数" | "パッド数" | "패드수" | "จำนวนแพด" =>
4805 {
4806 #[cfg(not(target_arch = "wasm32"))]
4807 {
4808 let inp = self.input.borrow();
4809 let n = inp.as_ref().map_or(0, |s| s.sensorium.devices.count());
4810 return Ok(Value::Number(n as f64));
4811 }
4812 #[cfg(target_arch = "wasm32")]
4813 return Ok(Value::Number(input_web::count() as f64));
4814 },
4815 "pad_connected" | "手柄连接" | "パッド接続" | "패드연결" | "แพดเชื่อม" =>
4817 {
4818 #[cfg(not(target_arch = "wasm32"))]
4819 {
4820 let i = self.arg_num(&args, 0, 0.0)? as usize;
4821 let inp = self.input.borrow();
4822 let c = inp
4823 .as_ref()
4824 .is_some_and(|s| s.sensorium.devices.for_player(i as u8).is_some());
4825 return Ok(Value::Bool(c));
4826 }
4827 #[cfg(target_arch = "wasm32")]
4828 {
4829 let i = self.arg_num(&args, 0, 0.0)? as usize;
4830 return Ok(Value::Bool(input_web::is_connected(i)));
4831 }
4832 },
4833 "pad_button" | "手柄按键" | "パッドボタン" | "패드버튼" | "ปุ่มแพด" =>
4835 {
4836 #[cfg(not(target_arch = "wasm32"))]
4837 {
4838 let i = self.arg_num(&args, 0, 0.0)? as usize;
4839 let name = self.arg_str(&args, 1, "");
4840 let down = parse_pad_button(&name)
4841 .is_some_and(|b| self.with_pad(i, false, |p| p.is_down(b)));
4842 return Ok(Value::Bool(down));
4843 }
4844 #[cfg(target_arch = "wasm32")]
4845 {
4846 let i = self.arg_num(&args, 0, 0.0)? as usize;
4847 let name = self.arg_str(&args, 1, "");
4848 return Ok(Value::Bool(input_web::button_down(i, &name)));
4849 }
4850 },
4851 "pad_pressed" | "手柄按下" | "パッド押下" | "패드눌림" | "แพดกด" =>
4854 {
4855 #[cfg(not(target_arch = "wasm32"))]
4856 {
4857 let i = self.arg_num(&args, 0, 0.0)? as usize;
4858 let name = self.arg_str(&args, 1, "");
4859 let p = parse_pad_button(&name)
4860 .is_some_and(|b| self.with_pad(i, false, |g| g.just_pressed(b)));
4861 return Ok(Value::Bool(p));
4862 }
4863 #[cfg(target_arch = "wasm32")]
4864 {
4865 let i = self.arg_num(&args, 0, 0.0)? as usize;
4866 let name = self.arg_str(&args, 1, "");
4867 return Ok(Value::Bool(input_web::button_down(i, &name)));
4868 }
4869 },
4870 "pad_lx" | "手柄左X" | "パッド左X" | "패드왼X" | "แพดซ้ายX" => {
4872 #[cfg(not(target_arch = "wasm32"))]
4873 {
4874 let i = self.arg_num(&args, 0, 0.0)? as usize;
4875 return Ok(Value::Number(
4876 self.with_pad(i, 0.0, |p| p.left_stick.x as f64),
4877 ));
4878 }
4879 #[cfg(target_arch = "wasm32")]
4880 {
4881 let i = self.arg_num(&args, 0, 0.0)? as usize;
4882 return Ok(Value::Number(input_web::axis_lx(i) as f64));
4883 }
4884 },
4885 "pad_ly" | "手柄左Y" | "パッド左Y" | "패드왼Y" | "แพดซ้ายY" => {
4886 #[cfg(not(target_arch = "wasm32"))]
4887 {
4888 let i = self.arg_num(&args, 0, 0.0)? as usize;
4889 return Ok(Value::Number(
4890 self.with_pad(i, 0.0, |p| p.left_stick.y as f64),
4891 ));
4892 }
4893 #[cfg(target_arch = "wasm32")]
4894 {
4895 let i = self.arg_num(&args, 0, 0.0)? as usize;
4896 return Ok(Value::Number(input_web::axis_ly(i) as f64));
4897 }
4898 },
4899 "pad_rx" | "手柄右X" | "パッド右X" | "패드오X" | "แพดขวาX" => {
4900 #[cfg(not(target_arch = "wasm32"))]
4901 {
4902 let i = self.arg_num(&args, 0, 0.0)? as usize;
4903 return Ok(Value::Number(
4904 self.with_pad(i, 0.0, |p| p.right_stick.x as f64),
4905 ));
4906 }
4907 #[cfg(target_arch = "wasm32")]
4908 {
4909 let i = self.arg_num(&args, 0, 0.0)? as usize;
4910 return Ok(Value::Number(input_web::axis_rx(i) as f64));
4911 }
4912 },
4913 "pad_ry" | "手柄右Y" | "パッド右Y" | "패드오Y" | "แพดขวาY" => {
4914 #[cfg(not(target_arch = "wasm32"))]
4915 {
4916 let i = self.arg_num(&args, 0, 0.0)? as usize;
4917 return Ok(Value::Number(
4918 self.with_pad(i, 0.0, |p| p.right_stick.y as f64),
4919 ));
4920 }
4921 #[cfg(target_arch = "wasm32")]
4922 {
4923 let i = self.arg_num(&args, 0, 0.0)? as usize;
4924 return Ok(Value::Number(input_web::axis_ry(i) as f64));
4925 }
4926 },
4927 "pad_lt" | "手柄左扳机" | "パッド左トリガー" | "패드왼트리거" | "ไกแพดซ้าย" =>
4929 {
4930 #[cfg(not(target_arch = "wasm32"))]
4931 {
4932 let i = self.arg_num(&args, 0, 0.0)? as usize;
4933 return Ok(Value::Number(
4934 self.with_pad(i, 0.0, |p| p.left_trigger as f64),
4935 ));
4936 }
4937 #[cfg(target_arch = "wasm32")]
4938 {
4939 let i = self.arg_num(&args, 0, 0.0)? as usize;
4940 return Ok(Value::Number(input_web::trigger_lt(i) as f64));
4941 }
4942 },
4943 "pad_rt" | "手柄右扳机" | "パッド右トリガー" | "패드오트리거" | "ไกแพดขวา" =>
4944 {
4945 #[cfg(not(target_arch = "wasm32"))]
4946 {
4947 let i = self.arg_num(&args, 0, 0.0)? as usize;
4948 return Ok(Value::Number(
4949 self.with_pad(i, 0.0, |p| p.right_trigger as f64),
4950 ));
4951 }
4952 #[cfg(target_arch = "wasm32")]
4953 {
4954 let i = self.arg_num(&args, 0, 0.0)? as usize;
4955 return Ok(Value::Number(input_web::trigger_rt(i) as f64));
4956 }
4957 },
4958 "pad_rumble" | "手柄震动" | "パッド振動" | "패드진동" | "แพดสั่น" =>
4960 {
4961 #[cfg(not(target_arch = "wasm32"))]
4962 {
4963 use ling_input::backend::InputBackend;
4964 let i = self.arg_num(&args, 0, 0.0)? as usize;
4965 let lo = self.arg_num(&args, 1, 0.0)? as f32;
4966 let hi = self.arg_num(&args, 2, lo as f64)? as f32;
4967 let mut inp = self.input.borrow_mut();
4968 if let Some(s) = inp.as_mut() {
4969 if let Some(dev) = s.sensorium.devices.for_player(i as u8).map(|d| d.id) {
4970 s.backend.set_rumble(
4971 dev,
4972 ling_input::Rumble { low: lo, high: hi, ..Default::default() },
4973 );
4974 }
4975 }
4976 return Ok(Value::Unit);
4977 }
4978 #[cfg(target_arch = "wasm32")]
4979 return Ok(Value::Unit);
4980 },
4981
4982 "set_camera_pos" | "ตั้งตำแหน่งกล้อง" | "镜坐标" | "カメラ座標" | "카메라좌표" =>
4984 {
4985 let x = self.arg_num(&args, 0, 0.0)? as f32;
4986 let y = self.arg_num(&args, 1, 0.0)? as f32;
4987 let z = self.arg_num(&args, 2, 0.0)? as f32;
4988 {
4989 let mut gfx = self.gfx.borrow_mut();
4990 gfx.camera.tx = x;
4991 gfx.camera.ty = y;
4992 gfx.camera.tz = z;
4993 }
4994 #[cfg(not(target_arch = "wasm32"))]
4995 if let Some(audio) = &self.audio {
4996 audio.set_listener_pos(x, y, z);
4997 }
4998 return Ok(Value::Unit);
4999 },
5000
5001 "move_camera" => {
5003 let dx = self.arg_num(&args, 0, 0.0)? as f32;
5004 let dy = self.arg_num(&args, 1, 0.0)? as f32;
5005 let dz = self.arg_num(&args, 2, 0.0)? as f32;
5006 let mut gfx = self.gfx.borrow_mut();
5007 gfx.camera.tx += dx;
5008 gfx.camera.ty += dy;
5009 gfx.camera.tz += dz;
5010 return Ok(Value::Unit);
5011 },
5012
5013 "set_zdist" | "ตั้งระยะห่าง" | "镜距" | "Z距離設定" | "Z거리설정" =>
5015 {
5016 let d = self.arg_num(&args, 0, 5.0)? as f32;
5017 self.gfx.borrow_mut().camera.zdist = d;
5018 return Ok(Value::Unit);
5019 },
5020
5021 "capture_mouse" | "จับเมาส์" | "捕鼠" | "マウス捕捉" | "마우스잡기" =>
5023 {
5024 #[cfg(not(target_arch = "wasm32"))]
5025 {
5026 let mut gfx = self.gfx.borrow_mut();
5027 gfx.mouse_captured = true;
5028 gfx.last_mx = f32::NAN;
5029 if let Some(win) = gfx.window.as_mut() {
5030 win.set_cursor_visibility(false);
5031 }
5032 }
5033 return Ok(Value::Unit);
5034 },
5035
5036 "release_mouse" => {
5038 #[cfg(not(target_arch = "wasm32"))]
5039 {
5040 let mut gfx = self.gfx.borrow_mut();
5041 gfx.mouse_captured = false;
5042 gfx.last_mx = f32::NAN;
5043 if let Some(win) = gfx.window.as_mut() {
5044 win.set_cursor_visibility(true);
5045 }
5046 #[cfg(windows)]
5047 unsafe {
5048 extern "system" {
5050 fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
5051 }
5052 ClipCursor(std::ptr::null());
5053 }
5054 }
5055 return Ok(Value::Unit);
5056 },
5057
5058 "cursor_hide" => {
5064 #[cfg(not(target_arch = "wasm32"))]
5065 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5066 win.set_cursor_visibility(false);
5067 }
5068 return Ok(Value::Unit);
5069 },
5070 "cursor_show" => {
5071 #[cfg(not(target_arch = "wasm32"))]
5072 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5073 win.set_cursor_visibility(true);
5074 }
5075 return Ok(Value::Unit);
5076 },
5077
5078 "set_camera" | "ตั้งกล้อง" | "设镜" | "设置摄像机" | "カメラ設定" | "카메라설정" =>
5085 {
5086 let cry = self.arg_num(&args, 0, 1.0)? as f32;
5087 let sry = self.arg_num(&args, 1, 0.0)? as f32;
5088 let crx = self.arg_num(&args, 2, 1.0)? as f32;
5089 let srx = self.arg_num(&args, 3, 0.0)? as f32;
5090 let mut gfx = self.gfx.borrow_mut();
5091 gfx.camera.cry = cry;
5092 gfx.camera.sry = sry;
5093 gfx.camera.crx = crx;
5094 gfx.camera.srx = srx;
5095 return Ok(Value::Unit);
5096 },
5097
5098 "set_projection" | "ตั้งโปรเจกชัน" | "投影" | "投影設定" | "투영설정" =>
5101 {
5102 let cx = self.arg_num(&args, 0, 960.0)? as f32;
5103 let cy = self.arg_num(&args, 1, 540.0)? as f32;
5104 let focal = self.arg_num(&args, 2, 1080.0)? as f32;
5105 let zdist = self.arg_num(&args, 3, 5.0)? as f32;
5106 let mut gfx = self.gfx.borrow_mut();
5107 gfx.camera.cx = cx;
5108 gfx.camera.cy = cy;
5109 gfx.camera.focal = focal;
5110 gfx.camera.zdist = zdist;
5111 return Ok(Value::Unit);
5112 },
5113
5114 "gltf_load" => {
5116 let path = self.arg_str(&args, 0, "");
5117 match ling_physics::gltf::GltfModel::load(&path) {
5118 Ok(m) => {
5119 self.gltf_models.borrow_mut().push(m);
5120 let h = self.gltf_models.borrow().len() - 1;
5121 return Ok(Value::Number(h as f64));
5122 }
5123 Err(e) => {
5124 eprintln!("mesh_load failed ({path}): {e}");
5125 return Ok(Value::Number(-1.0));
5126 }
5127 }
5128 },
5129 "gltf_anim_count" => {
5131 let h = self.arg_num(&args, 0, -1.0)? as i64;
5132 let n = self
5133 .gltf_models
5134 .borrow()
5135 .get(h as usize)
5136 .map(|m| m.animations.len())
5137 .unwrap_or(0);
5138 return Ok(Value::Number(n as f64));
5139 },
5140 "gltf_anim_name" => {
5142 let h = self.arg_num(&args, 0, -1.0)? as i64;
5143 let i = self.arg_num(&args, 1, 0.0)? as usize;
5144 let s = self
5145 .gltf_models
5146 .borrow()
5147 .get(h as usize)
5148 .and_then(|m| m.animations.get(i))
5149 .map(|a| a.name.clone())
5150 .unwrap_or_default();
5151 return Ok(Value::Str(s));
5152 },
5153 "gltf_anim_dur" => {
5155 let h = self.arg_num(&args, 0, -1.0)? as i64;
5156 let i = self.arg_num(&args, 1, 0.0)? as usize;
5157 let d = self
5158 .gltf_models
5159 .borrow()
5160 .get(h as usize)
5161 .and_then(|m| m.animations.get(i))
5162 .map(|a| a.duration)
5163 .unwrap_or(0.0);
5164 return Ok(Value::Number(d as f64));
5165 },
5166 "gltf_tris" => {
5168 let h = self.arg_num(&args, 0, -1.0)? as i64;
5169 let n: usize = self
5170 .gltf_models
5171 .borrow()
5172 .get(h as usize)
5173 .map(|m| m.meshes.iter().map(|mm| mm.indices.len()).sum::<usize>() / 3)
5174 .unwrap_or(0);
5175 return Ok(Value::Number(n as f64));
5176 },
5177
5178 "gltf_joint_count" => {
5180 let h = self.arg_num(&args, 0, -1.0)? as i64;
5181 let n = self
5182 .gltf_models
5183 .borrow()
5184 .get(h as usize)
5185 .and_then(|m| m.skins.first())
5186 .map(|s| s.joints.len())
5187 .unwrap_or(0);
5188 return Ok(Value::Number(n as f64));
5189 },
5190 "gltf_joint_name" => {
5192 let h = self.arg_num(&args, 0, -1.0)? as i64;
5193 let j = self.arg_num(&args, 1, 0.0)? as usize;
5194 let models = self.gltf_models.borrow();
5195 let s = models
5196 .get(h as usize)
5197 .and_then(|m| {
5198 m.skins
5199 .first()
5200 .and_then(|sk| sk.joints.get(j))
5201 .and_then(|jt| m.nodes.get(jt.node_idx))
5202 .map(|n| n.name.clone())
5203 })
5204 .unwrap_or_default();
5205 return Ok(Value::Str(s));
5206 },
5207
5208 "gltf_draw" => {
5213 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5214 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5215 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5216 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5217 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5218 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5219 let (sy, cyy) = yaw.sin_cos();
5220 let models = self.gltf_models.borrow();
5221 let model = match models.get(hh as usize) {
5222 Some(m) => m,
5223 None => return Ok(Value::Unit),
5224 };
5225 let mut gfx = self.gfx.borrow_mut();
5226 let cp = {
5227 let c = &gfx.camera;
5228 ling_gpu::CameraParams {
5229 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5230 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5231 tx: c.tx, ty: c.ty, tz: c.tz,
5232 }
5233 };
5234 let near = -gfx.camera.zdist + 0.02;
5235 let ambient = gfx.ambient;
5236 for mesh in &model.meshes {
5237 let nlow = mesh.name.to_lowercase();
5238 let base: u32 = if nlow.contains("hair") {
5239 0x7a4a28
5240 } else if nlow.contains("cloth") || nlow.contains("top") {
5241 0x4a86e0
5242 } else if nlow.contains("wing") {
5243 0xe6ecf5
5244 } else if nlow.contains("star") {
5245 0xffd24d
5246 } else {
5247 0xf2d6b8
5248 };
5249 let nv = mesh.verts.len();
5250 if nv == 0 {
5251 continue;
5252 }
5253 let mut world = vec![0.0f32; nv * 3];
5254 for (i, v) in mesh.verts.iter().enumerate() {
5255 let gx = v.pos.x * scale;
5256 let gy = -v.pos.y * scale;
5257 let gz = -v.pos.z * scale;
5258 let rx = gx * cyy + gz * sy;
5259 let rz = -gx * sy + gz * cyy;
5260 world[i * 3] = ox + rx;
5261 world[i * 3 + 1] = oy + gy;
5262 world[i * 3 + 2] = oz + rz;
5263 }
5264 let mut proj = vec![0.0f32; nv * 3];
5265 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5266 let idx = &mesh.indices;
5267 let nt = idx.len() / 3;
5268 for t in 0..nt {
5269 let ia = idx[t * 3] as usize;
5270 let ib = idx[t * 3 + 1] as usize;
5271 let ic = idx[t * 3 + 2] as usize;
5272 if ia >= nv || ib >= nv || ic >= nv {
5273 continue;
5274 }
5275 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5276 if (da + db + dc) / 3.0 <= near {
5277 continue;
5278 }
5279 let col = {
5280 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5281 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5282 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5283 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5284 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5285 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5286 let centroid =
5287 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5288 if gfx.flat_shade {
5289 base
5290 } else {
5291 crate::gfx::light::compute_lit_color(
5292 base, normal, centroid, &gfx.lights, ambient,
5293 )
5294 }
5295 };
5296 let depth = (da + db + dc) / 3.0;
5297 let col = gfx.fog_apply(col, depth);
5298 gfx.depth_queue.push_triangle_zv(
5299 col,
5300 proj[ia * 3], proj[ia * 3 + 1], da,
5301 proj[ib * 3], proj[ib * 3 + 1], db,
5302 proj[ic * 3], proj[ic * 3 + 1], dc,
5303 );
5304 }
5305 }
5306 return Ok(Value::Unit);
5307 },
5308
5309 "gltf_autorig" => {
5311 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5312 let mut models = self.gltf_models.borrow_mut();
5313 if let Some(m) = models.get_mut(hh as usize) {
5314 return Ok(Value::Number(m.autorig() as f64));
5315 }
5316 return Ok(Value::Number(0.0));
5317 },
5318
5319 "gltf_pose_draw" => {
5323 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5324 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5325 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5326 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5327 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5328 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5329 let euler: Vec<f32> = match args.get(6) {
5330 Some(Value::List(v)) => {
5331 v.iter().map(|x| self.to_number(x).unwrap_or(0.0) as f32).collect()
5332 },
5333 _ => Vec::new(),
5334 };
5335 let (sy, cyy) = yaw.sin_cos();
5336 let models = self.gltf_models.borrow();
5337 let model = match models.get(hh as usize) {
5338 Some(m) => m,
5339 None => return Ok(Value::Unit),
5340 };
5341 let skinned = model.skin_local(&euler);
5342 let mut gfx = self.gfx.borrow_mut();
5343 let cp = {
5344 let c = &gfx.camera;
5345 ling_gpu::CameraParams {
5346 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5347 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5348 tx: c.tx, ty: c.ty, tz: c.tz,
5349 }
5350 };
5351 let near = -gfx.camera.zdist + 0.02;
5352 let ambient = gfx.ambient;
5353 for (mi, mesh) in model.meshes.iter().enumerate() {
5354 let nlow = mesh.name.to_lowercase();
5355 let base: u32 = if nlow.contains("hair") {
5356 0x7a4a28
5357 } else if nlow.contains("cloth") || nlow.contains("top") {
5358 0x4a86e0
5359 } else if nlow.contains("wing") {
5360 0xe6ecf5
5361 } else if nlow.contains("star") {
5362 0xffd24d
5363 } else {
5364 0xf2d6b8
5365 };
5366 let sk = match skinned.get(mi) {
5367 Some(s) => s,
5368 None => continue,
5369 };
5370 let nv = sk.len();
5371 if nv == 0 {
5372 continue;
5373 }
5374 let mut world = vec![0.0f32; nv * 3];
5375 for i in 0..nv {
5376 let gx = sk[i][0] * scale;
5377 let gy = -sk[i][1] * scale;
5378 let gz = -sk[i][2] * scale;
5379 let rx = gx * cyy + gz * sy;
5380 let rz = -gx * sy + gz * cyy;
5381 world[i * 3] = ox + rx;
5382 world[i * 3 + 1] = oy + gy;
5383 world[i * 3 + 2] = oz + rz;
5384 }
5385 let mut proj = vec![0.0f32; nv * 3];
5386 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5387 let idx = &mesh.indices;
5388 let nt = idx.len() / 3;
5389 for t in 0..nt {
5390 let ia = idx[t * 3] as usize;
5391 let ib = idx[t * 3 + 1] as usize;
5392 let ic = idx[t * 3 + 2] as usize;
5393 if ia >= nv || ib >= nv || ic >= nv {
5394 continue;
5395 }
5396 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5397 if (da + db + dc) / 3.0 <= near {
5398 continue;
5399 }
5400 let col = {
5401 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5402 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5403 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5404 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5405 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5406 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5407 let centroid =
5408 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5409 if gfx.flat_shade {
5410 base
5411 } else {
5412 crate::gfx::light::compute_lit_color(
5413 base, normal, centroid, &gfx.lights, ambient,
5414 )
5415 }
5416 };
5417 let depth = (da + db + dc) / 3.0;
5418 let col = gfx.fog_apply(col, depth);
5419 gfx.depth_queue.push_triangle_zv(
5420 col,
5421 proj[ia * 3], proj[ia * 3 + 1], da,
5422 proj[ib * 3], proj[ib * 3 + 1], db,
5423 proj[ic * 3], proj[ic * 3 + 1], dc,
5424 );
5425 }
5426 }
5427 return Ok(Value::Unit);
5428 },
5429
5430 "draw_mesh" | "วาดเมช" => {
5437 let pos = match args.first() {
5438 Some(Value::List(v)) => v,
5439 _ => return Ok(Value::Unit),
5440 };
5441 let idx = match args.get(1) {
5442 Some(Value::List(v)) => v,
5443 _ => return Ok(Value::Unit),
5444 };
5445 let ox = self.arg_num(&args, 2, 0.0)? as f32;
5446 let oy = self.arg_num(&args, 3, 0.0)? as f32;
5447 let oz = self.arg_num(&args, 4, 0.0)? as f32;
5448 let scale = self.arg_num(&args, 5, 1.0)? as f32;
5449 let mode = self.arg_num(&args, 6, 0.0)? as i64;
5450 let nv = pos.len() / 3;
5451 if nv == 0 {
5452 return Ok(Value::Unit);
5453 }
5454 let mut world = vec![0.0f32; nv * 3];
5455 for i in 0..nv {
5456 world[i * 3] = ox + self.to_number(&pos[i * 3]).unwrap_or(0.0) as f32 * scale;
5457 world[i * 3 + 1] =
5458 oy + self.to_number(&pos[i * 3 + 1]).unwrap_or(0.0) as f32 * scale;
5459 world[i * 3 + 2] =
5460 oz + self.to_number(&pos[i * 3 + 2]).unwrap_or(0.0) as f32 * scale;
5461 }
5462 let mut gfx = self.gfx.borrow_mut();
5463 let cp = {
5464 let c = &gfx.camera;
5465 ling_gpu::CameraParams {
5466 cry: c.cry,
5467 sry: c.sry,
5468 crx: c.crx,
5469 srx: c.srx,
5470 cx: c.cx,
5471 cy: c.cy,
5472 focal: c.focal,
5473 zdist: c.zdist,
5474 tx: c.tx,
5475 ty: c.ty,
5476 tz: c.tz,
5477 }
5478 };
5479 let near = -gfx.camera.zdist + 0.02;
5480 let base = gfx.color;
5481 let ambient = gfx.ambient;
5482 let mut proj = vec![0.0f32; nv * 3]; ling_gpu::backend().project_points(&world, &cp, &mut proj);
5484 let nt = idx.len() / 3;
5485 for t in 0..nt {
5486 let ia = self.to_number(&idx[t * 3]).unwrap_or(0.0) as usize;
5487 let ib = self.to_number(&idx[t * 3 + 1]).unwrap_or(0.0) as usize;
5488 let ic = self.to_number(&idx[t * 3 + 2]).unwrap_or(0.0) as usize;
5489 if ia >= nv || ib >= nv || ic >= nv {
5490 continue;
5491 }
5492 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5493 if (da + db + dc) / 3.0 <= near {
5494 continue;
5495 } let col = if mode == 1 {
5497 let h = t as f32 * 0.6;
5498 let r = ((h.sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5499 let g = (((h + 2.094).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5500 let b = (((h + 4.189).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5501 (r << 16) | (g << 8) | b
5502 } else {
5503 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5504 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5505 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5506 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5507 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5508 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5509 let centroid = [
5510 (ax + bx + px) / 3.0,
5511 (ay + by + py) / 3.0,
5512 (az + bz + pz) / 3.0,
5513 ];
5514 if gfx.flat_shade {
5515 base
5516 } else {
5517 crate::gfx::light::compute_lit_color(
5518 base,
5519 normal,
5520 centroid,
5521 &gfx.lights,
5522 ambient,
5523 )
5524 }
5525 };
5526 let depth = (da + db + dc) / 3.0;
5527 let col = gfx.fog_apply(col, depth);
5528 gfx.depth_queue.push_triangle_zv(
5532 col,
5533 proj[ia * 3],
5534 proj[ia * 3 + 1],
5535 da,
5536 proj[ib * 3],
5537 proj[ib * 3 + 1],
5538 db,
5539 proj[ic * 3],
5540 proj[ic * 3 + 1],
5541 dc,
5542 );
5543 }
5544 return Ok(Value::Unit);
5545 },
5546
5547 "add_light" | "เพิ่มแสง" | "加灯" | "ライト追加" | "조명추가" =>
5551 {
5552 let x = self.arg_num(&args, 0, 0.0)? as f32;
5553 let y = self.arg_num(&args, 1, -3.0)? as f32;
5554 let z = self.arg_num(&args, 2, 3.0)? as f32;
5555 let mut r = self.arg_num(&args, 3, 1.0)? as f32;
5556 let mut g = self.arg_num(&args, 4, 1.0)? as f32;
5557 let mut b = self.arg_num(&args, 5, 1.0)? as f32;
5558 if r > 1.5 || g > 1.5 || b > 1.5 {
5561 r /= 255.0;
5562 g /= 255.0;
5563 b /= 255.0;
5564 }
5565 let intensity = self.arg_num(&args, 6, 1.0)? as f32;
5566 let radius = self.arg_num(&args, 7, 0.0)? as f32;
5567 self.gfx
5568 .borrow_mut()
5569 .lights
5570 .push(Light { x, y, z, r, g, b, intensity, radius });
5571 return Ok(Value::Unit);
5572 },
5573
5574 "clear_lights" | "ล้างแสง" | "清灯" | "ライト消去" | "조명초기화" =>
5576 {
5577 self.gfx.borrow_mut().lights.clear();
5578 return Ok(Value::Unit);
5579 },
5580
5581 "set_material" | "ตั้งวัสดุ" | "设置材质" | "マテリアル設定" | "재질설정" =>
5591 {
5592 let key = self.arg_str(&args, 0, "");
5593 let val = self.arg_num(&args, 1, 0.0)?;
5594 let mut gfx = self.gfx.borrow_mut();
5595 let mat = gfx
5596 .material
5597 .get_or_insert_with(crate::gfx::LingMaterial::default);
5598 match key.as_str() {
5599 "albedo" => mat.albedo = val as u32,
5600 "roughness" => mat.roughness = val as f32,
5601 "metallic" => mat.metallic = val as f32,
5602 "emission" => mat.emission = val as u32,
5603 "emission_strength" => mat.emission_strength = val as f32,
5604 "specular" => mat.specular = val as f32,
5605 "specular_tint" => mat.specular_tint = val as f32,
5606 "subsurface" => mat.subsurface = val as f32,
5607 "subsurface_color" => mat.subsurface_color = val as u32,
5608 "clearcoat" => mat.clearcoat = val as f32,
5609 "clearcoat_roughness" => mat.clearcoat_roughness = val as f32,
5610 "transmission" => mat.transmission = val as f32,
5611 "ior" => mat.ior = val as f32,
5612 "iridescence" => mat.iridescence = val as f32,
5613 "sheen" => mat.sheen = val as f32,
5614 "anisotropy" => mat.anisotropy = val as f32,
5615 "anisotropy_angle" => mat.anisotropy_angle = val as f32,
5616 "toon_bands" => mat.toon_bands = val as u32,
5617 "shadow_softness" => mat.shadow_softness = val as f32,
5618 "outline_px" => mat.outline_px = val as f32,
5619 "outline_color" => mat.outline_color = val as u32,
5620 "highlight_color" => mat.highlight_color = val as u32,
5621 _ => {},
5622 }
5623 return Ok(Value::Unit);
5624 },
5625
5626 "reset_material" | "รีเซ็ตวัสดุ" | "重置材质" | "マテリアルリセット" | "재질초기화" =>
5629 {
5630 self.gfx.borrow_mut().material = None;
5631 return Ok(Value::Unit);
5632 },
5633
5634 "toon_outlines"
5640 | "ตั้งเส้นขอบการ์ตูน"
5641 | "卡通轮廓"
5642 | "トゥーンアウトライン"
5643 | "툰아웃라인" => {
5644 let px = self.arg_num(&args, 0, 0.0)? as f32;
5645 let color = self.arg_num(&args, 1, 0.0)? as u32;
5646 let thresh = self.arg_num(&args, 2, 0.05)? as f32;
5647 let mut gfx = self.gfx.borrow_mut();
5648 gfx.toon.outline_px = px;
5649 gfx.toon.outline_color = color;
5650 gfx.toon.outline_thresh = thresh;
5651 return Ok(Value::Unit);
5652 },
5653
5654 "tone_stop" | "ตั้งจุดโทน" | "色调停止" | "トーンストップ" | "톤스톱" =>
5660 {
5661 let t = self.arg_num(&args, 0, 0.0)? as f32;
5662 let val = self.arg_num(&args, 1, 1.0)? as f32;
5663 let mut gfx = self.gfx.borrow_mut();
5664 gfx.toon.ramp.stops.push(crate::gfx::toon::ToneStop {
5665 t: t.clamp(0.0, 1.0),
5666 value: val.clamp(0.0, 1.0),
5667 });
5668 gfx.toon
5669 .ramp
5670 .stops
5671 .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
5672 return Ok(Value::Unit);
5673 },
5674
5675 "tone_smooth" | "ตั้งโทนนุ่ม" | "色调平滑" | "トーンスムーズ" | "톤스무스" =>
5678 {
5679 let v = self.arg_num(&args, 0, 0.0)? as f32;
5680 self.gfx.borrow_mut().toon.ramp.smooth = v > 0.5;
5681 return Ok(Value::Unit);
5682 },
5683
5684 "tone_bezier" | "ตั้งโทนเบซิเยร์" | "色调贝塞尔" | "トーンベジェ" | "톤베지어" =>
5692 {
5693 let y1 = self.arg_num(&args, 0, 1.0 / 3.0)? as f32;
5694 let y2 = self.arg_num(&args, 1, 2.0 / 3.0)? as f32;
5695 self.gfx.borrow_mut().toon.ramp.bezier = Some([y1, y2]);
5696 return Ok(Value::Unit);
5697 },
5698
5699 "tone_bezier_off"
5701 | "ปิดโทนเบซิเยร์"
5702 | "关闭色调贝塞尔"
5703 | "トーンベジェオフ"
5704 | "톤베지어끄기" => {
5705 self.gfx.borrow_mut().toon.ramp.bezier = None;
5706 return Ok(Value::Unit);
5707 },
5708
5709 "tone_ramp_reset"
5711 | "รีเซ็ตการไล่โทน"
5712 | "重置色调渐变"
5713 | "トーンランプリセット"
5714 | "톤램프리셋" => {
5715 self.gfx.borrow_mut().toon.ramp = crate::gfx::toon::ToneRamp::default();
5716 return Ok(Value::Unit);
5717 },
5718
5719 "tone_ramp_clear"
5721 | "ล้างการไล่โทน"
5722 | "清除色调渐变"
5723 | "トーンランプクリア"
5724 | "톤램프클리어" => {
5725 self.gfx.borrow_mut().toon.ramp.stops.clear();
5726 return Ok(Value::Unit);
5727 },
5728
5729 "tone_soft" | "โทนขอบนุ่ม" | "色调柔边" | "トーンソフト" | "톤소프트" => {
5737 let s = self.arg_num(&args, 0, 0.32)? as f32;
5738 let sh = self.arg_num(&args, 1, 0.65)? as f32;
5739 let mut gfx = self.gfx.borrow_mut();
5740 gfx.toon.ramp.soft = s.clamp(0.0, 1.0);
5741 gfx.toon.ramp.sheen = sh.clamp(0.0, 1.0);
5742 return Ok(Value::Unit);
5743 },
5744
5745 "set_ssao" | "ตั้งเงาสัมผัส" | "环境光遮蔽" | "アンビエントオクルージョン"
5750 | "앰비언트오클루전" => {
5751 let s = self.arg_num(&args, 0, 0.35)? as f32;
5752 let r = self.arg_num(&args, 1, 6.0)? as f32;
5753 let z = self.arg_num(&args, 2, 12.0)? as f32;
5754 let mut gfx = self.gfx.borrow_mut();
5755 gfx.toon.ao_strength = s.clamp(0.0, 1.0);
5756 gfx.toon.ao_radius = r.max(1.0);
5757 gfx.toon.ao_range = z.max(0.01);
5758 return Ok(Value::Unit);
5759 },
5760
5761 "set_fxaa" | "ลบรอยหยัก" | "屏幕抗锯齿" | "画面アンチエイリアス" | "화면안티앨리어싱" => {
5767 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
5768 self.gfx.borrow_mut().toon.fxaa = on;
5769 return Ok(Value::Unit);
5770 },
5771
5772 "set_bloom" | "ตั้งบลูม" | "泛光" | "ブルーム" | "블룸" => {
5777 let s = self.arg_num(&args, 0, 0.45)? as f32;
5778 let t = self.arg_num(&args, 1, 0.74)? as f32;
5779 let mut gfx = self.gfx.borrow_mut();
5780 gfx.toon.bloom_strength = s.max(0.0);
5781 gfx.toon.bloom_thresh = t.clamp(0.0, 0.99);
5782 return Ok(Value::Unit);
5783 },
5784
5785 "shadow_smooth" | "ตั้งเงานุ่ม" | "柔化阴影" | "影ソフト" | "그림자부드럽게" =>
5788 {
5789 let s = self.arg_num(&args, 0, 0.0)? as f32;
5790 let mut gfx = self.gfx.borrow_mut();
5791 gfx.toon.ramp.smooth = s > 0.05;
5792 if s > 0.05 {
5793 let y1 = (0.333 + s * 0.2).clamp(0.0, 1.0);
5794 let y2 = (0.667 - s * 0.2).clamp(0.0, 1.0);
5795 gfx.toon.ramp.bezier = Some([y1, y2]);
5796 } else {
5797 gfx.toon.ramp.bezier = None;
5798 }
5799 return Ok(Value::Unit);
5800 },
5801
5802 "toon_highlight"
5804 | "ตั้งไฮไลท์การ์ตูน"
5805 | "卡通高光"
5806 | "トゥーンハイライト"
5807 | "툰하이라이트" => {
5808 let _strength = self.arg_num(&args, 0, 0.0)? as f32;
5810 let _thresh = self.arg_num(&args, 2, 0.78)? as f32;
5811 return Ok(Value::Unit);
5813 },
5814
5815 "set_ambient" | "ตั้งแสงรอบข้าง" | "环境光" | "環境光設定" | "환경광설정" =>
5817 {
5818 let v = self.arg_num(&args, 0, 0.15)? as f32;
5819 self.gfx.borrow_mut().ambient = v;
5820 return Ok(Value::Unit);
5821 },
5822
5823 "set_fog" | "ตั้งหมอก" | "雾" | "霧設定" | "안개설정" => {
5826 let r = self.arg_num(&args, 0, 0.0)?.clamp(0.0, 255.0) as u32;
5827 let g = self.arg_num(&args, 1, 0.0)?.clamp(0.0, 255.0) as u32;
5828 let b = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 255.0) as u32;
5829 let start = self.arg_num(&args, 3, 0.0)? as f32;
5830 let end = self.arg_num(&args, 4, 0.0)? as f32;
5831 let mut gfx = self.gfx.borrow_mut();
5832 gfx.fog_color = (r << 16) | (g << 8) | b;
5833 gfx.fog_start = start;
5834 gfx.fog_end = end;
5835 return Ok(Value::Unit);
5836 },
5837
5838 "วาดสามเหลี่ยม3มิติ" | "draw_triangle_3d" | "triangle3d" =>
5842 {
5843 let ax = self.arg_num(&args, 0, 0.0)? as f32;
5844 let ay = self.arg_num(&args, 1, 0.0)? as f32;
5845 let az = self.arg_num(&args, 2, 0.0)? as f32;
5846 let bx = self.arg_num(&args, 3, 0.0)? as f32;
5847 let by = self.arg_num(&args, 4, 0.0)? as f32;
5848 let bz = self.arg_num(&args, 5, 0.0)? as f32;
5849 let cx = self.arg_num(&args, 6, 0.0)? as f32;
5850 let cy = self.arg_num(&args, 7, 0.0)? as f32;
5851 let cz = self.arg_num(&args, 8, 0.0)? as f32;
5852
5853 let mut gfx = self.gfx.borrow_mut();
5854
5855 if gfx.mesh_capture.is_some() {
5857 let col = gfx.color;
5858 gfx.mesh_capture
5859 .as_mut()
5860 .unwrap()
5861 .push(([ax, ay, az, bx, by, bz, cx, cy, cz], col));
5862 return Ok(Value::Unit);
5863 }
5864
5865 gfx.submit_triangle(ax, ay, az, bx, by, bz, cx, cy, cz);
5866 return Ok(Value::Unit);
5867 },
5868
5869 "เริ่มอบเมช" | "mesh_bake_begin" => {
5871 self.gfx.borrow_mut().mesh_capture = Some(Vec::new());
5872 return Ok(Value::Unit);
5873 },
5874
5875 "เมชแคชรับ" | "mesh_cache_get" => {
5877 let key = self.arg_num(&args, 0, 0.0)? as i64;
5878 let h = self.gfx.borrow().mesh_cache.get(&key).copied();
5879 return Ok(Value::Number(h.map(|x| x as f64).unwrap_or(-1.0)));
5880 },
5881
5882 "เมชแคชตั้ง" | "mesh_cache_put" => {
5884 let key = self.arg_num(&args, 0, 0.0)? as i64;
5885 let h = self.arg_num(&args, 1, 0.0)? as usize;
5886 let mut gfx = self.gfx.borrow_mut();
5887 const CAP: usize = 256;
5888 if gfx.mesh_cache.len() >= CAP {
5889 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5890 gfx.mesh_cache.clear();
5891 for id in evict {
5892 if id < gfx.meshes.len() {
5893 gfx.meshes[id].clear();
5894 gfx.mesh_free.push(id);
5895 }
5896 }
5897 }
5898 gfx.mesh_cache.insert(key, h);
5899 return Ok(Value::Unit);
5900 },
5901
5902 "เมชแคชล้าง" | "mesh_cache_clear" => {
5904 let mut gfx = self.gfx.borrow_mut();
5905 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5906 gfx.mesh_cache.clear();
5907 for id in evict {
5908 if id < gfx.meshes.len() {
5909 gfx.meshes[id].clear();
5910 gfx.mesh_free.push(id);
5911 }
5912 }
5913 return Ok(Value::Unit);
5914 },
5915
5916 "จบอบเมช" | "mesh_bake_end" => {
5918 let mut gfx = self.gfx.borrow_mut();
5919 let tris = gfx.mesh_capture.take().unwrap_or_default();
5920 let id = gfx.mesh_register(tris);
5921 return Ok(Value::Number(id as f64));
5922 },
5923
5924 "วาดอบเมช" | "mesh_bake_draw" | "วาดอบเมชสี" | "mesh_bake_draw_col" =>
5928 {
5929 let baked_col = matches!(name, "วาดอบเมชสี" | "mesh_bake_draw_col");
5930 let id = self.arg_num(&args, 0, 0.0)? as usize;
5931 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5932 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5933 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5934 let rx = self.arg_num(&args, 4, 1.0)? as f32;
5935 let ry = self.arg_num(&args, 5, 0.0)? as f32;
5936 let rz = self.arg_num(&args, 6, 0.0)? as f32;
5937 let ux = self.arg_num(&args, 7, 0.0)? as f32;
5938 let uy = self.arg_num(&args, 8, 1.0)? as f32;
5939 let uz = self.arg_num(&args, 9, 0.0)? as f32;
5940 let s = self.arg_num(&args, 10, 1.0)? as f32;
5941 self.gfx
5942 .borrow_mut()
5943 .mesh_draw(id, ox, oy, oz, rx, ry, rz, ux, uy, uz, s, baked_col);
5944 return Ok(Value::Unit);
5945 },
5946
5947 "draw_quad_3d"
5951 | "quad3d"
5952 | "วาดสี่เหลี่ยม3มิติ"
5953 | "draw_pent_3d"
5954 | "pent3d"
5955 | "วาดห้าเหลี่ยม3มิติ"
5956 | "draw_hex_3d"
5957 | "hex3d"
5958 | "วาดหกเหลี่ยม3มิติ"
5959 | "draw_polygon_3d"
5960 | "polygon3d"
5961 | "วาดรูปหลายเหลี่ยม3มิติ" => {
5962 let mut wxs: [f32; 8] = [0.0; 8];
5964 let mut wys: [f32; 8] = [0.0; 8];
5965 let mut wzs: [f32; 8] = [0.0; 8];
5966 let n_verts;
5967
5968 if args.len() == 1 {
5969 let list = match &args[0] {
5971 Value::List(l) => l.clone(),
5972 _ => {
5973 return Err(EvalErr::from("draw_polygon_3d: expected list".to_string()))
5974 },
5975 };
5976 let coords: Vec<f32> = list
5977 .iter()
5978 .map(|v| match v {
5979 Value::Number(n) => *n as f32,
5980 _ => 0.0,
5981 })
5982 .collect();
5983 n_verts = (coords.len() / 3).min(8);
5984 for i in 0..n_verts {
5985 wxs[i] = coords[i * 3];
5986 wys[i] = coords[i * 3 + 1];
5987 wzs[i] = coords[i * 3 + 2];
5988 }
5989 } else {
5990 n_verts = (args.len() / 3).min(8);
5992 for i in 0..n_verts {
5993 wxs[i] = self.arg_num(&args, i * 3, 0.0)? as f32;
5994 wys[i] = self.arg_num(&args, i * 3 + 1, 0.0)? as f32;
5995 wzs[i] = self.arg_num(&args, i * 3 + 2, 0.0)? as f32;
5996 }
5997 }
5998 if n_verts < 3 {
5999 return Ok(Value::Unit);
6000 }
6001
6002 let mut gfx = self.gfx.borrow_mut();
6003
6004 if gfx.mesh_capture.is_some() {
6010 let col = gfx.color;
6011 let cap = gfx.mesh_capture.as_mut().unwrap();
6012 for i in 1..n_verts - 1 {
6013 cap.push((
6014 [
6015 wxs[0], wys[0], wzs[0],
6016 wxs[i], wys[i], wzs[i],
6017 wxs[i + 1], wys[i + 1], wzs[i + 1],
6018 ],
6019 col,
6020 ));
6021 }
6022 return Ok(Value::Unit);
6023 }
6024
6025 let normal = crate::gfx::poly::face_normal(
6027 wxs[0], wys[0], wzs[0], wxs[1], wys[1], wzs[1], wxs[2], wys[2], wzs[2],
6028 );
6029
6030 let mut wcs: [u32; 8] = [0; 8];
6032 if gfx.flat_shade {
6033 let c = gfx.color;
6034 for wc in wcs.iter_mut().take(n_verts) {
6035 *wc = c;
6036 }
6037 } else if let Some(ref mat) = gfx.material.clone() {
6038 let cam = [gfx.camera.cx, gfx.camera.cy, gfx.camera.zdist];
6039 let lights: Vec<_> = gfx.lights.clone();
6040 let ambient = gfx.ambient;
6041 for i in 0..n_verts {
6042 let v = [wxs[i], wys[i], wzs[i]];
6043 let vd = [cam[0] - v[0], cam[1] - v[1], cam[2] - v[2]];
6044 wcs[i] = crate::gfx::material::shade(mat, normal, vd, v, &lights, ambient);
6045 }
6046 } else {
6047 let base = gfx.color;
6048 let lights: Vec<_> = gfx.lights.clone();
6049 let ambient = gfx.ambient;
6050 for i in 0..n_verts {
6051 wcs[i] = crate::gfx::light::compute_lit_color_linear(
6052 base,
6053 normal,
6054 [wxs[i], wys[i], wzs[i]],
6055 &lights,
6056 ambient,
6057 );
6058 }
6059 }
6060
6061 let near = -gfx.camera.zdist + 0.05;
6063 let mut clip_in: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6064 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6065 for i in 0..n_verts {
6066 let d = gfx.camera.depth(wxs[i], wys[i], wzs[i]);
6067 clip_in[i] = (wxs[i], wys[i], wzs[i], d, wcs[i]);
6068 }
6069 let mut clip_out: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6070 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6071 let pn = crate::gfx::poly::clip_near(&clip_in, n_verts, near, &mut clip_out);
6072 if pn < 3 {
6073 return Ok(Value::Unit);
6074 }
6075
6076 let mut proj: [(f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6078 [(0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6079 for i in 0..pn {
6080 let (sx, sy, sz) =
6081 gfx.camera
6082 .project(clip_out[i].0, clip_out[i].1, clip_out[i].2);
6083 let fc = gfx.fog_apply(clip_out[i].4, sz);
6084 proj[i] = (sx, sy, sz, fc);
6085 }
6086
6087 let unlit = gfx.flat_shade;
6089 crate::gfx::poly::fan_emit_proj(
6090 &proj,
6091 pn,
6092 |x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2| {
6093 gfx.depth_queue.push_triangle_g_zv(
6094 x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2, 3, unlit,
6095 );
6096 },
6097 );
6098 return Ok(Value::Unit);
6099 },
6100
6101 "วาดเส้น3มิติ" | "draw_line_3d" | "line3d" | "画3D线" | "3D線描く" | "3D선그리기" =>
6105 {
6106 let ax = self.arg_num(&args, 0, 0.0)? as f32;
6107 let ay = self.arg_num(&args, 1, 0.0)? as f32;
6108 let az = self.arg_num(&args, 2, 0.0)? as f32;
6109 let bx = self.arg_num(&args, 3, 0.0)? as f32;
6110 let by = self.arg_num(&args, 4, 0.0)? as f32;
6111 let bz = self.arg_num(&args, 5, 0.0)? as f32;
6112
6113 let mut gfx = self.gfx.borrow_mut();
6114 let color = gfx.color;
6115 let near = -gfx.camera.zdist + 0.05;
6117 let mut lax = ax;
6118 let mut lay = ay;
6119 let mut laz = az;
6120 let mut lbx = bx;
6121 let mut lby = by;
6122 let mut lbz = bz;
6123 let da_raw = gfx.camera.depth(lax, lay, laz);
6124 let db_raw = gfx.camera.depth(lbx, lby, lbz);
6125 if da_raw <= near && db_raw <= near {
6126 return Ok(Value::Unit);
6127 }
6128 if da_raw <= near {
6129 let t = (near - da_raw) / (db_raw - da_raw);
6130 lax += t * (lbx - lax);
6131 lay += t * (lby - lay);
6132 laz += t * (lbz - laz);
6133 } else if db_raw <= near {
6134 let t = (near - da_raw) / (db_raw - da_raw);
6135 lbx = lax + t * (lbx - lax);
6136 lby = lay + t * (lby - lay);
6137 lbz = laz + t * (lbz - laz);
6138 }
6139 if !gfx.edge_set.try_insert(lax, lay, laz, lbx, lby, lbz) {
6141 return Ok(Value::Unit);
6142 }
6143 let (sax, say, da) = gfx.camera.project(lax, lay, laz);
6144 let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
6145 let depth = (da + db) / 2.0;
6146 let color = gfx.fog_apply(color, depth);
6147 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
6148 return Ok(Value::Unit);
6149 },
6150
6151 #[cfg(not(target_arch = "wasm32"))]
6161 "orb_shell" | "球壳" | "オーブ殻" | "오브껍질" | "เปลือกทรงกลม" =>
6162 {
6163 let cx = self.arg_num(&args, 0, 0.)? as f32;
6164 let cy = self.arg_num(&args, 1, 0.)? as f32;
6165 let cz = self.arg_num(&args, 2, 0.)? as f32;
6166 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6167 let ry = self.arg_num(&args, 4, 0.)? as f32;
6168 let rx = self.arg_num(&args, 5, 0.)? as f32;
6169 let density = (self.arg_num(&args, 6, 10.)? as i32).clamp(1, 48);
6170 let tr = (self.arg_num(&args, 7, 230.)? as f32).clamp(0., 255.);
6171 let tg = (self.arg_num(&args, 8, 230.)? as f32).clamp(0., 255.);
6172 let tb = (self.arg_num(&args, 9, 235.)? as f32).clamp(0., 255.);
6173 let (cyr, syr) = (ry.cos(), ry.sin());
6174 let (cxr, sxr) = (rx.cos(), rx.sin());
6175 let tau = std::f32::consts::TAU;
6176 let pi = std::f32::consts::PI;
6177 let turns = 6.0_f32; let nseg = 96; let inv_r = if radius.abs() > 1e-5 {
6180 1.0 / radius
6181 } else {
6182 0.0
6183 };
6184 let pt = |u: f32, theta0: f32, dir: f32| -> ([f32; 3], f32) {
6187 let phi = pi * u; let th = dir * turns * tau * u + theta0;
6189 let (mut x, y, mut z) = (
6190 phi.sin() * th.cos() * radius,
6191 phi.cos() * radius,
6192 phi.sin() * th.sin() * radius,
6193 );
6194 let x1 = x * cyr + z * syr; let z1 = -x * syr + z * cyr;
6196 x = x1;
6197 z = z1;
6198 let y2 = y * cxr - z * sxr; let z2 = y * sxr + z * cxr;
6200 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.0, 1.0);
6202 ([cx + x, cy + y2, cz + z2], facing)
6203 };
6204 let mut gfx = self.gfx.borrow_mut();
6205 let near = -gfx.camera.zdist + 0.05;
6206 let seg = |gfx: &mut crate::gfx::GfxState, a: [f32; 3], b: [f32; 3], lum: f32| {
6208 let (mut lax, mut lay, mut laz) = (a[0], a[1], a[2]);
6209 let (mut lbx, mut lby, mut lbz) = (b[0], b[1], b[2]);
6210 let da = gfx.camera.depth(lax, lay, laz);
6211 let db = gfx.camera.depth(lbx, lby, lbz);
6212 if da <= near && db <= near {
6213 return;
6214 }
6215 if da <= near {
6216 let t = (near - da) / (db - da);
6217 lax += t * (lbx - lax);
6218 lay += t * (lby - lay);
6219 laz += t * (lbz - laz);
6220 } else if db <= near {
6221 let t = (near - da) / (db - da);
6222 lbx = lax + t * (lbx - lax);
6223 lby = lay + t * (lby - lay);
6224 lbz = laz + t * (lbz - laz);
6225 }
6226 let (sax, say, da2) = gfx.camera.project(lax, lay, laz);
6227 let (sbx, sby, db2) = gfx.camera.project(lbx, lby, lbz);
6228 let l = (0.12 + 0.88 * lum).clamp(0.0, 1.0);
6230 let cr = (tr * l) as u32;
6231 let cg = (tg * l) as u32;
6232 let cb = (tb * l) as u32;
6233 let color = (cr << 16) | (cg << 8) | cb;
6234 gfx.depth_queue
6235 .push_line((da2 + db2) * 0.5, color, sax, say, sbx, sby);
6236 };
6237 for &dir in &[1.0_f32, -1.0_f32] {
6239 for s in 0..density {
6240 let theta0 = s as f32 * tau / density as f32;
6241 let mut prev = pt(0.0, theta0, dir);
6242 for k in 1..=nseg {
6243 let cur = pt(k as f32 / nseg as f32, theta0, dir);
6244 seg(&mut gfx, prev.0, cur.0, (prev.1 + cur.1) * 0.5);
6245 prev = cur;
6246 }
6247 }
6248 }
6249 return Ok(Value::Unit);
6250 },
6251
6252 #[cfg(not(target_arch = "wasm32"))]
6260 "orb_particles" | "球内粒子" | "オーブ粒子" | "오브입자" | "อนุภาคทรงกลม" =>
6261 {
6262 let cx = self.arg_num(&args, 0, 0.)? as f32;
6263 let cy = self.arg_num(&args, 1, 0.)? as f32;
6264 let cz = self.arg_num(&args, 2, 0.)? as f32;
6265 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6266 let count = (self.arg_num(&args, 4, 160.)? as i32).clamp(1, 4000);
6267 let t = self.arg_num(&args, 5, 0.)? as f32;
6268 let tr = (self.arg_num(&args, 6, 255.)? as f32).clamp(0., 255.);
6269 let tg = (self.arg_num(&args, 7, 255.)? as f32).clamp(0., 255.);
6270 let tb = (self.arg_num(&args, 8, 255.)? as f32).clamp(0., 255.);
6271 let inv_r = if radius.abs() > 1e-5 {
6272 1.0 / radius
6273 } else {
6274 0.0
6275 };
6276 let h = |mut x: u32| -> f32 {
6278 x = x.wrapping_mul(747796405).wrapping_add(2891336453);
6279 x = ((x >> ((x >> 28).wrapping_add(4))) ^ x).wrapping_mul(277803737);
6280 (((x >> 22) ^ x) & 0xFFFFFF) as f32 / 16_777_216.0
6281 };
6282 let tau = std::f32::consts::TAU;
6283 let (cyr, syr) = ((t * 0.5).cos(), (t * 0.5).sin());
6285 let (cxr, sxr) = ((t * 0.23).cos(), (t * 0.23).sin());
6286 let mut gfx = self.gfx.borrow_mut();
6287 let near = -gfx.camera.zdist + 0.05;
6288 let (sw, sh) = (gfx.width as i32, gfx.height as i32);
6289 for i in 0..count {
6290 let i = i as u32;
6291 let u = h(i.wrapping_mul(3) + 1);
6293 let rr = u.cbrt() * radius * (0.85 + 0.15 * (t * 1.3 + i as f32).sin()); let th = h(i.wrapping_mul(3) + 2) * tau + t * (0.3 + 0.5 * h(i * 7 + 5)); let ph = (h(i.wrapping_mul(3) + 3) * 2.0 - 1.0).acos(); let (mut x, y, mut z) = (
6297 rr * ph.sin() * th.cos(),
6298 rr * ph.cos(),
6299 rr * ph.sin() * th.sin(),
6300 );
6301 let x1 = x * cyr + z * syr;
6303 let z1 = -x * syr + z * cyr;
6304 x = x1;
6305 z = z1;
6306 let y2 = y * cxr - z * sxr;
6307 let z2 = y * sxr + z * cxr;
6308 let (wx, wy, wz) = (cx + x, cy + y2, cz + z2);
6309 if gfx.camera.depth(wx, wy, wz) <= near {
6310 continue;
6311 }
6312 let (sx, sy, dep) = gfx.camera.project(wx, wy, wz);
6313 let sxi = sx as i32;
6314 let syi = sy as i32;
6315 if sxi < 0 || syi < 0 || sxi >= sw || syi >= sh {
6316 continue;
6317 }
6318 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.15, 1.0);
6320 let l = facing;
6321 let cr = (tr * l) as u32;
6322 let cg = (tg * l) as u32;
6323 let cb = (tb * l) as u32;
6324 let color = (cr << 16) | (cg << 8) | cb;
6325 let len = if facing > 0.7 { 1.0 } else { 0.0 };
6327 gfx.depth_queue.push_line(dep, color, sx, sy, sx + len, sy);
6328 }
6329 return Ok(Value::Unit);
6330 },
6331
6332 "project_3d" | "投影3D" | "3D投影" | "3D투영" | "ฉาย3มิติ" => {
6336 let x = self.arg_num(&args, 0, 0.0)? as f32;
6337 let y = self.arg_num(&args, 1, 0.0)? as f32;
6338 let z = self.arg_num(&args, 2, 0.0)? as f32;
6339 let gfx = self.gfx.borrow();
6340 let near = -gfx.camera.zdist + 0.05;
6341 let d = gfx.camera.depth(x, y, z);
6342 if d <= near {
6343 return Ok(Value::List(Rc::new(vec![
6344 Value::Number(-99999.0),
6345 Value::Number(-99999.0),
6346 Value::Number(d as f64),
6347 ])));
6348 }
6349 let (sx, sy, depth) = gfx.camera.project(x, y, z);
6350 return Ok(Value::List(Rc::new(vec![
6351 Value::Number(sx as f64),
6352 Value::Number(sy as f64),
6353 Value::Number(depth as f64),
6354 ])));
6355 },
6356
6357 #[cfg(not(target_arch = "wasm32"))]
6364 "mouse_ray" => {
6365 let gfx = self.gfx.borrow();
6366 let (mx, my) = gfx
6367 .window
6368 .as_ref()
6369 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
6370 .unwrap_or((gfx.camera.cx, gfx.camera.cy));
6371 let cam = &gfx.camera;
6372 let dcx = (mx - cam.cx) / cam.focal;
6375 let dcy = (my - cam.cy) / cam.focal;
6376 let a_x = dcx;
6379 let a_y = cam.crx * dcy + cam.srx * 1.0;
6380 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6381 let dir_x = cam.cry * a_x + cam.sry * a_z;
6382 let dir_y = a_y;
6383 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6384 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6385 .sqrt()
6386 .max(1e-6);
6387 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6388 let ox = cam.tx;
6400 let oy = cam.ty;
6401 let oz = cam.tz;
6402 return Ok(Value::List(Rc::new(vec![
6403 Value::Number(ox as f64),
6404 Value::Number(oy as f64),
6405 Value::Number(oz as f64),
6406 Value::Number(dir_x as f64),
6407 Value::Number(dir_y as f64),
6408 Value::Number(dir_z as f64),
6409 ])));
6410 },
6411 #[cfg(target_arch = "wasm32")]
6412 "mouse_ray" => {
6413 let gfx = self.gfx.borrow();
6414 let mx = crate::gfx::wasm_mouse_x();
6415 let my = crate::gfx::wasm_mouse_y();
6416 let cam = &gfx.camera;
6417 let dcx = (mx - cam.cx) / cam.focal;
6418 let dcy = (my - cam.cy) / cam.focal;
6419 let a_x = dcx;
6420 let a_y = cam.crx * dcy + cam.srx * 1.0;
6421 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6422 let dir_x = cam.cry * a_x + cam.sry * a_z;
6423 let dir_y = a_y;
6424 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6425 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6426 .sqrt()
6427 .max(1e-6);
6428 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6429 let ox = cam.tx;
6430 let oy = cam.ty;
6431 let oz = cam.tz;
6432 return Ok(Value::List(Rc::new(vec![
6433 Value::Number(ox as f64),
6434 Value::Number(oy as f64),
6435 Value::Number(oz as f64),
6436 Value::Number(dir_x as f64),
6437 Value::Number(dir_y as f64),
6438 Value::Number(dir_z as f64),
6439 ])));
6440 },
6441 #[cfg(not(target_arch = "wasm32"))]
6444 "draw_poly" | "填充多边形" | "ポリゴン塗り" | "다각형채우기" | "เติมรูปหลายเหลี่ยม" =>
6445 {
6446 let mut pts: Vec<[f32; 2]> = Vec::new();
6447 if let Some(Value::List(v)) = args.first() {
6448 let mut i = 0;
6449 while i + 1 < v.len() {
6450 let x = self.to_number(&v[i]).unwrap_or(0.0) as f32;
6451 let y = self.to_number(&v[i + 1]).unwrap_or(0.0) as f32;
6452 pts.push([x, y]);
6453 i += 2;
6454 }
6455 }
6456 if pts.len() >= 3 {
6457 if pts[0] != pts[pts.len() - 1] {
6458 let p0 = pts[0];
6459 pts.push(p0);
6460 } let mut gfx = self.gfx.borrow_mut();
6462 let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
6463 crate::gfx::raster::fill_contours_aa(
6464 &mut gfx.buffer,
6465 w,
6466 h,
6467 color,
6468 add,
6469 std::slice::from_ref(&pts),
6470 );
6471 }
6472 return Ok(Value::Unit);
6473 },
6474
6475 "vtex_grid" | "ลายตาราง" | "纹格" | "格子模様" | "격자무늬" =>
6484 {
6485 let cx = self.arg_num(&args, 0, 0.)? as f32;
6486 let cy = self.arg_num(&args, 1, 0.)? as f32;
6487 let cz = self.arg_num(&args, 2, 0.)? as f32;
6488 let ux = self.arg_num(&args, 3, 1.)? as f32;
6489 let uy = self.arg_num(&args, 4, 0.)? as f32;
6490 let uz = self.arg_num(&args, 5, 0.)? as f32;
6491 let vx = self.arg_num(&args, 6, 0.)? as f32;
6492 let vy = self.arg_num(&args, 7, 0.)? as f32;
6493 let vz = self.arg_num(&args, 8, 1.)? as f32;
6494 let cols = self.arg_num(&args, 9, 10.)? as usize;
6495 let rows = self.arg_num(&args, 10, 10.)? as usize;
6496 let cw = self.arg_num(&args, 11, 1.)? as f32;
6497 let ch = self.arg_num(&args, 12, 1.)? as f32;
6498 let fr = self.arg_num(&args, 13, 0.)? as f32;
6499 let hue = self.arg_num(&args, 14, 0.)? as f32;
6500 let mut gfx = self.gfx.borrow_mut();
6501 let cam = gfx.camera.clone();
6502 crate::gfx::vtex::draw_grid(
6503 &mut gfx.depth_queue,
6504 &cam,
6505 cx,
6506 cy,
6507 cz,
6508 ux,
6509 uy,
6510 uz,
6511 vx,
6512 vy,
6513 vz,
6514 cols,
6515 rows,
6516 cw,
6517 ch,
6518 fr,
6519 hue,
6520 );
6521 return Ok(Value::Unit);
6522 },
6523
6524 "vtex_rings" | "ลายวงซ้อน" | "纹环" | "同心円" | "동심원" => {
6526 let cx = self.arg_num(&args, 0, 0.)? as f32;
6527 let cy = self.arg_num(&args, 1, 0.)? as f32;
6528 let cz = self.arg_num(&args, 2, 0.)? as f32;
6529 let ux = self.arg_num(&args, 3, 1.)? as f32;
6530 let uy = self.arg_num(&args, 4, 0.)? as f32;
6531 let uz = self.arg_num(&args, 5, 0.)? as f32;
6532 let vx = self.arg_num(&args, 6, 0.)? as f32;
6533 let vy = self.arg_num(&args, 7, 0.)? as f32;
6534 let vz = self.arg_num(&args, 8, 1.)? as f32;
6535 let nr = self.arg_num(&args, 9, 6.)? as usize;
6536 let ns = self.arg_num(&args, 10, 6.)? as usize;
6537 let mr = self.arg_num(&args, 11, 3.)? as f32;
6538 let tw = self.arg_num(&args, 12, 0.)? as f32;
6539 let fr = self.arg_num(&args, 13, 0.)? as f32;
6540 let hue = self.arg_num(&args, 14, 0.)? as f32;
6541 let mut gfx = self.gfx.borrow_mut();
6542 let cam = gfx.camera.clone();
6543 crate::gfx::vtex::draw_rings(
6544 &mut gfx.depth_queue,
6545 &cam,
6546 cx,
6547 cy,
6548 cz,
6549 ux,
6550 uy,
6551 uz,
6552 vx,
6553 vy,
6554 vz,
6555 nr,
6556 ns,
6557 mr,
6558 tw,
6559 fr,
6560 hue,
6561 );
6562 return Ok(Value::Unit);
6563 },
6564
6565 "vtex_star" | "ลายดาว" | "纹星" | "星模様" | "별무늬" => {
6567 let cx = self.arg_num(&args, 0, 0.)? as f32;
6568 let cy = self.arg_num(&args, 1, 0.)? as f32;
6569 let cz = self.arg_num(&args, 2, 0.)? as f32;
6570 let ux = self.arg_num(&args, 3, 1.)? as f32;
6571 let uy = self.arg_num(&args, 4, 0.)? as f32;
6572 let uz = self.arg_num(&args, 5, 0.)? as f32;
6573 let vx = self.arg_num(&args, 6, 0.)? as f32;
6574 let vy = self.arg_num(&args, 7, 0.)? as f32;
6575 let vz = self.arg_num(&args, 8, 1.)? as f32;
6576 let np = self.arg_num(&args, 9, 6.)? as usize;
6577 let ro = self.arg_num(&args, 10, 2.)? as f32;
6578 let ri = self.arg_num(&args, 11, 1.)? as f32;
6579 let rs = self.arg_num(&args, 12, 0.01)? as f32;
6580 let fr = self.arg_num(&args, 13, 0.)? as f32;
6581 let hue = self.arg_num(&args, 14, 0.)? as f32;
6582 let mut gfx = self.gfx.borrow_mut();
6583 let cam = gfx.camera.clone();
6584 crate::gfx::vtex::draw_star(
6585 &mut gfx.depth_queue,
6586 &cam,
6587 cx,
6588 cy,
6589 cz,
6590 ux,
6591 uy,
6592 uz,
6593 vx,
6594 vy,
6595 vz,
6596 np,
6597 ro,
6598 ri,
6599 rs,
6600 fr,
6601 hue,
6602 );
6603 return Ok(Value::Unit);
6604 },
6605
6606 "vtex_spiral" | "ลายเกลียว" | "纹螺" | "螺旋" | "나선" => {
6608 let cx = self.arg_num(&args, 0, 0.)? as f32;
6609 let cy = self.arg_num(&args, 1, 0.)? as f32;
6610 let cz = self.arg_num(&args, 2, 0.)? as f32;
6611 let ux = self.arg_num(&args, 3, 1.)? as f32;
6612 let uy = self.arg_num(&args, 4, 0.)? as f32;
6613 let uz = self.arg_num(&args, 5, 0.)? as f32;
6614 let vx = self.arg_num(&args, 6, 0.)? as f32;
6615 let vy = self.arg_num(&args, 7, 0.)? as f32;
6616 let vz = self.arg_num(&args, 8, 1.)? as f32;
6617 let nt = self.arg_num(&args, 9, 3.)? as f32;
6618 let mr = self.arg_num(&args, 10, 3.)? as f32;
6619 let st = self.arg_num(&args, 11, 120.)? as usize;
6620 let fr = self.arg_num(&args, 12, 0.)? as f32;
6621 let hue = self.arg_num(&args, 13, 0.)? as f32;
6622 let mut gfx = self.gfx.borrow_mut();
6623 let cam = gfx.camera.clone();
6624 crate::gfx::vtex::draw_spiral(
6625 &mut gfx.depth_queue,
6626 &cam,
6627 cx,
6628 cy,
6629 cz,
6630 ux,
6631 uy,
6632 uz,
6633 vx,
6634 vy,
6635 vz,
6636 nt,
6637 mr,
6638 st,
6639 fr,
6640 hue,
6641 );
6642 return Ok(Value::Unit);
6643 },
6644
6645 "vtex_flower" | "ลายดอก" | "纹花" | "花模様" | "꽃무늬" => {
6647 let cx = self.arg_num(&args, 0, 0.)? as f32;
6648 let cy = self.arg_num(&args, 1, 0.)? as f32;
6649 let cz = self.arg_num(&args, 2, 0.)? as f32;
6650 let ux = self.arg_num(&args, 3, 1.)? as f32;
6651 let uy = self.arg_num(&args, 4, 0.)? as f32;
6652 let uz = self.arg_num(&args, 5, 0.)? as f32;
6653 let vx = self.arg_num(&args, 6, 0.)? as f32;
6654 let vy = self.arg_num(&args, 7, 0.)? as f32;
6655 let vz = self.arg_num(&args, 8, 1.)? as f32;
6656 let r = self.arg_num(&args, 9, 1.)? as f32;
6657 let ns = self.arg_num(&args, 10, 24.)? as usize;
6658 let fr = self.arg_num(&args, 11, 0.)? as f32;
6659 let hue = self.arg_num(&args, 12, 0.)? as f32;
6660 let mut gfx = self.gfx.borrow_mut();
6661 let cam = gfx.camera.clone();
6662 crate::gfx::vtex::draw_flower(
6663 &mut gfx.depth_queue,
6664 &cam,
6665 cx,
6666 cy,
6667 cz,
6668 ux,
6669 uy,
6670 uz,
6671 vx,
6672 vy,
6673 vz,
6674 r,
6675 ns,
6676 fr,
6677 hue,
6678 );
6679 return Ok(Value::Unit);
6680 },
6681
6682 "vtex_letter_rain" | "ลายอักษรไหล" | "纹字雨" | "文字雨" | "글자비" =>
6684 {
6685 let cx = self.arg_num(&args, 0, 0.)? as f32;
6686 let cy = self.arg_num(&args, 1, 0.)? as f32;
6687 let cz = self.arg_num(&args, 2, 0.)? as f32;
6688 let ux = self.arg_num(&args, 3, 1.)? as f32;
6689 let uy = self.arg_num(&args, 4, 0.)? as f32;
6690 let uz = self.arg_num(&args, 5, 0.)? as f32;
6691 let vx = self.arg_num(&args, 6, 0.)? as f32;
6692 let vy = self.arg_num(&args, 7, 0.)? as f32;
6693 let vz = self.arg_num(&args, 8, 1.)? as f32;
6694 let nc = self.arg_num(&args, 9, 16.)? as usize;
6695 let nv = self.arg_num(&args, 10, 14.)? as usize;
6696 let cw = self.arg_num(&args, 11, 0.65)? as f32;
6697 let rh = self.arg_num(&args, 12, 0.60)? as f32;
6698 let sp = self.arg_num(&args, 13, 0.025)? as f32;
6699 let fr = self.arg_num(&args, 14, 0.)? as f32;
6700 let hue = self.arg_num(&args, 15, 0.)? as f32;
6701 let mut gfx = self.gfx.borrow_mut();
6702 let cam = gfx.camera.clone();
6703 crate::gfx::vtex::draw_letter_rain(
6704 &mut gfx.depth_queue,
6705 &cam,
6706 cx,
6707 cy,
6708 cz,
6709 ux,
6710 uy,
6711 uz,
6712 vx,
6713 vy,
6714 vz,
6715 nc,
6716 nv,
6717 cw,
6718 rh,
6719 sp,
6720 fr,
6721 hue,
6722 );
6723 return Ok(Value::Unit);
6724 },
6725
6726 "vtex_hyperbolic_uv" | "ลายไฮเพอร์โบลิก" | "纹曲面" | "双曲線" | "쌍곡선" =>
6728 {
6729 let cx = self.arg_num(&args, 0, 0.)? as f32;
6730 let cy = self.arg_num(&args, 1, 0.)? as f32;
6731 let cz = self.arg_num(&args, 2, 0.)? as f32;
6732 let ux = self.arg_num(&args, 3, 1.)? as f32;
6733 let uy = self.arg_num(&args, 4, 0.)? as f32;
6734 let uz = self.arg_num(&args, 5, 0.)? as f32;
6735 let vx = self.arg_num(&args, 6, 0.)? as f32;
6736 let vy = self.arg_num(&args, 7, 0.)? as f32;
6737 let vz = self.arg_num(&args, 8, 1.)? as f32;
6738 let mr = self.arg_num(&args, 9, 5.)? as f32;
6739 let nc = self.arg_num(&args, 10, 12.)? as usize;
6740 let nr = self.arg_num(&args, 11, 18.)? as usize;
6741 let fr = self.arg_num(&args, 12, 0.)? as f32;
6742 let hue = self.arg_num(&args, 13, 0.)? as f32;
6743 let mut gfx = self.gfx.borrow_mut();
6744 let cam = gfx.camera.clone();
6745 crate::gfx::vtex::draw_hyperbolic_uv(
6746 &mut gfx.depth_queue,
6747 &cam,
6748 cx,
6749 cy,
6750 cz,
6751 ux,
6752 uy,
6753 uz,
6754 vx,
6755 vy,
6756 vz,
6757 mr,
6758 nc,
6759 nr,
6760 fr,
6761 hue,
6762 );
6763 return Ok(Value::Unit);
6764 },
6765
6766 "vtex_halftone" | "ลายจุด" | "纹半调" | "網点模様" | "망점" => {
6768 let cx = self.arg_num(&args, 0, 0.)? as f32;
6769 let cy = self.arg_num(&args, 1, 0.)? as f32;
6770 let cz = self.arg_num(&args, 2, 0.)? as f32;
6771 let ux = self.arg_num(&args, 3, 1.)? as f32;
6772 let uy = self.arg_num(&args, 4, 0.)? as f32;
6773 let uz = self.arg_num(&args, 5, 0.)? as f32;
6774 let vx = self.arg_num(&args, 6, 0.)? as f32;
6775 let vy = self.arg_num(&args, 7, 0.)? as f32;
6776 let vz = self.arg_num(&args, 8, 1.)? as f32;
6777 let cols = self.arg_num(&args, 9, 16.)? as usize;
6778 let rows = self.arg_num(&args, 10, 12.)? as usize;
6779 let cw = self.arg_num(&args, 11, 0.5)? as f32;
6780 let ch = self.arg_num(&args, 12, 0.5)? as f32;
6781 let dens = self.arg_num(&args, 13, 0.4)? as f32;
6782 let fr = self.arg_num(&args, 14, 0.)? as f32;
6783 let hue = self.arg_num(&args, 15, 0.)? as f32;
6784 let mut gfx = self.gfx.borrow_mut();
6785 let cam = gfx.camera.clone();
6786 crate::gfx::vtex::draw_halftone(
6787 &mut gfx.depth_queue,
6788 &cam,
6789 cx,
6790 cy,
6791 cz,
6792 ux,
6793 uy,
6794 uz,
6795 vx,
6796 vy,
6797 vz,
6798 cols,
6799 rows,
6800 cw,
6801 ch,
6802 dens,
6803 fr,
6804 hue,
6805 );
6806 return Ok(Value::Unit);
6807 },
6808
6809 "vtex_tessellated" | "ลายตาข่าย" | "纹镶嵌" | "網目模様" | "격자망" =>
6811 {
6812 let cx = self.arg_num(&args, 0, 0.)? as f32;
6813 let cy = self.arg_num(&args, 1, 0.)? as f32;
6814 let cz = self.arg_num(&args, 2, 0.)? as f32;
6815 let ux = self.arg_num(&args, 3, 1.)? as f32;
6816 let uy = self.arg_num(&args, 4, 0.)? as f32;
6817 let uz = self.arg_num(&args, 5, 0.)? as f32;
6818 let vx = self.arg_num(&args, 6, 0.)? as f32;
6819 let vy = self.arg_num(&args, 7, 0.)? as f32;
6820 let vz = self.arg_num(&args, 8, 1.)? as f32;
6821 let cols = self.arg_num(&args, 9, 14.)? as usize;
6822 let rows = self.arg_num(&args, 10, 10.)? as usize;
6823 let cell = self.arg_num(&args, 11, 0.6)? as f32;
6824 let amp = self.arg_num(&args, 12, 0.25)? as f32;
6825 let freq = self.arg_num(&args, 13, 4.)? as f32;
6826 let fr = self.arg_num(&args, 14, 0.)? as f32;
6827 let hue = self.arg_num(&args, 15, 0.)? as f32;
6828 let mut gfx = self.gfx.borrow_mut();
6829 let cam = gfx.camera.clone();
6830 crate::gfx::vtex::draw_tessellated(
6831 &mut gfx.depth_queue,
6832 &cam,
6833 cx,
6834 cy,
6835 cz,
6836 ux,
6837 uy,
6838 uz,
6839 vx,
6840 vy,
6841 vz,
6842 cols,
6843 rows,
6844 cell,
6845 amp,
6846 freq,
6847 fr,
6848 hue,
6849 );
6850 return Ok(Value::Unit);
6851 },
6852
6853 "vtex_lotus" | "ลายดอกบัว" | "纹莲" | "蓮模様" | "연꽃무늬" =>
6855 {
6856 let cx = self.arg_num(&args, 0, 0.)? as f32;
6857 let cy = self.arg_num(&args, 1, 0.)? as f32;
6858 let cz = self.arg_num(&args, 2, 0.)? as f32;
6859 let ux = self.arg_num(&args, 3, 1.)? as f32;
6860 let uy = self.arg_num(&args, 4, 0.)? as f32;
6861 let uz = self.arg_num(&args, 5, 0.)? as f32;
6862 let vx = self.arg_num(&args, 6, 0.)? as f32;
6863 let vy = self.arg_num(&args, 7, 0.)? as f32;
6864 let vz = self.arg_num(&args, 8, 1.)? as f32;
6865 let ri = self.arg_num(&args, 9, 1.)? as f32;
6866 let ro = self.arg_num(&args, 10, 2.)? as f32;
6867 let np = self.arg_num(&args, 11, 12.)? as usize;
6868 let fr = self.arg_num(&args, 12, 0.)? as f32;
6869 let hue = self.arg_num(&args, 13, 0.)? as f32;
6870 let mut gfx = self.gfx.borrow_mut();
6871 let cam = gfx.camera.clone();
6872 crate::gfx::vtex::draw_lotus(
6873 &mut gfx.depth_queue,
6874 &cam,
6875 cx,
6876 cy,
6877 cz,
6878 ux,
6879 uy,
6880 uz,
6881 vx,
6882 vy,
6883 vz,
6884 ri,
6885 ro,
6886 np,
6887 fr,
6888 hue,
6889 );
6890 return Ok(Value::Unit);
6891 },
6892
6893 "vtex_chakra" | "ลายจักร" | "纹轮" | "輪模様" | "바퀴무늬" => {
6895 let cx = self.arg_num(&args, 0, 0.)? as f32;
6896 let cy = self.arg_num(&args, 1, 0.)? as f32;
6897 let cz = self.arg_num(&args, 2, 0.)? as f32;
6898 let ux = self.arg_num(&args, 3, 1.)? as f32;
6899 let uy = self.arg_num(&args, 4, 0.)? as f32;
6900 let uz = self.arg_num(&args, 5, 0.)? as f32;
6901 let vx = self.arg_num(&args, 6, 0.)? as f32;
6902 let vy = self.arg_num(&args, 7, 0.)? as f32;
6903 let vz = self.arg_num(&args, 8, 1.)? as f32;
6904 let r = self.arg_num(&args, 9, 2.)? as f32;
6905 let ns = self.arg_num(&args, 10, 8.)? as usize;
6906 let fr = self.arg_num(&args, 11, 0.)? as f32;
6907 let hue = self.arg_num(&args, 12, 0.)? as f32;
6908 let mut gfx = self.gfx.borrow_mut();
6909 let cam = gfx.camera.clone();
6910 crate::gfx::vtex::draw_chakra(
6911 &mut gfx.depth_queue,
6912 &cam,
6913 cx,
6914 cy,
6915 cz,
6916 ux,
6917 uy,
6918 uz,
6919 vx,
6920 vy,
6921 vz,
6922 r,
6923 ns,
6924 fr,
6925 hue,
6926 );
6927 return Ok(Value::Unit);
6928 },
6929
6930 "vtex_yantra" | "ลายยันต์" | "纹咒" | "護符模様" | "부적무늬" =>
6932 {
6933 let cx = self.arg_num(&args, 0, 0.)? as f32;
6934 let cy = self.arg_num(&args, 1, 0.)? as f32;
6935 let cz = self.arg_num(&args, 2, 0.)? as f32;
6936 let ux = self.arg_num(&args, 3, 1.)? as f32;
6937 let uy = self.arg_num(&args, 4, 0.)? as f32;
6938 let uz = self.arg_num(&args, 5, 0.)? as f32;
6939 let vx = self.arg_num(&args, 6, 0.)? as f32;
6940 let vy = self.arg_num(&args, 7, 0.)? as f32;
6941 let vz = self.arg_num(&args, 8, 1.)? as f32;
6942 let nl = self.arg_num(&args, 9, 4.)? as usize;
6943 let mr = self.arg_num(&args, 10, 3.)? as f32;
6944 let fr = self.arg_num(&args, 11, 0.)? as f32;
6945 let hue = self.arg_num(&args, 12, 0.)? as f32;
6946 let mut gfx = self.gfx.borrow_mut();
6947 let cam = gfx.camera.clone();
6948 crate::gfx::vtex::draw_yantra(
6949 &mut gfx.depth_queue,
6950 &cam,
6951 cx,
6952 cy,
6953 cz,
6954 ux,
6955 uy,
6956 uz,
6957 vx,
6958 vy,
6959 vz,
6960 nl,
6961 mr,
6962 fr,
6963 hue,
6964 );
6965 return Ok(Value::Unit);
6966 },
6967
6968 "vtex_spiked_cog" | "ฟันเฟืองหนาม" | "纹棘轮" | "歯車模様" | "톱니바퀴" =>
6970 {
6971 let cx = self.arg_num(&args, 0, 0.)? as f32;
6972 let cy = self.arg_num(&args, 1, 0.)? as f32;
6973 let cz = self.arg_num(&args, 2, 0.)? as f32;
6974 let ux = self.arg_num(&args, 3, 1.)? as f32;
6975 let uy = self.arg_num(&args, 4, 0.)? as f32;
6976 let uz = self.arg_num(&args, 5, 0.)? as f32;
6977 let vx = self.arg_num(&args, 6, 0.)? as f32;
6978 let vy = self.arg_num(&args, 7, 0.)? as f32;
6979 let vz = self.arg_num(&args, 8, 1.)? as f32;
6980 let nt = self.arg_num(&args, 9, 12.)? as usize;
6981 let rb = self.arg_num(&args, 10, 1.)? as f32;
6982 let rs = self.arg_num(&args, 11, 1.3)? as f32;
6983 let rh = self.arg_num(&args, 12, 0.2)? as f32;
6984 let ns = self.arg_num(&args, 13, 6.)? as usize;
6985 let fr = self.arg_num(&args, 14, 0.)? as f32;
6986 let hue = self.arg_num(&args, 15, 0.)? as f32;
6987 let mut gfx = self.gfx.borrow_mut();
6988 let cam = gfx.camera.clone();
6989 crate::gfx::vtex::draw_spiked_cog(
6990 &mut gfx.depth_queue,
6991 &cam,
6992 cx,
6993 cy,
6994 cz,
6995 ux,
6996 uy,
6997 uz,
6998 vx,
6999 vy,
7000 vz,
7001 nt,
7002 rb,
7003 rs,
7004 rh,
7005 ns,
7006 fr,
7007 hue,
7008 );
7009 return Ok(Value::Unit);
7010 },
7011
7012 "vtex_torii" | "ประตูโทริอิ" | "纹鸟居" | "鳥居" | "도리이" =>
7014 {
7015 let cx = self.arg_num(&args, 0, 0.)? as f32;
7016 let cy = self.arg_num(&args, 1, 0.)? as f32;
7017 let cz = self.arg_num(&args, 2, 0.)? as f32;
7018 let ux = self.arg_num(&args, 3, 1.)? as f32;
7019 let uy = self.arg_num(&args, 4, 0.)? as f32;
7020 let uz = self.arg_num(&args, 5, 0.)? as f32;
7021 let vx = self.arg_num(&args, 6, 0.)? as f32;
7022 let vy = self.arg_num(&args, 7, 0.)? as f32;
7023 let vz = self.arg_num(&args, 8, 1.)? as f32;
7024 let w = self.arg_num(&args, 9, 4.)? as f32;
7025 let h = self.arg_num(&args, 10, 5.)? as f32;
7026 let fr = self.arg_num(&args, 11, 0.)? as f32;
7027 let hue = self.arg_num(&args, 12, 0.)? as f32;
7028 let mut gfx = self.gfx.borrow_mut();
7029 let cam = gfx.camera.clone();
7030 crate::gfx::vtex::draw_torii(
7031 &mut gfx.depth_queue,
7032 &cam,
7033 cx,
7034 cy,
7035 cz,
7036 ux,
7037 uy,
7038 uz,
7039 vx,
7040 vy,
7041 vz,
7042 w,
7043 h,
7044 fr,
7045 hue,
7046 );
7047 return Ok(Value::Unit);
7048 },
7049
7050 "vtex_pagoda" | "เจดีย์" | "纹塔" | "塔" | "탑" => {
7052 let cx = self.arg_num(&args, 0, 0.)? as f32;
7053 let cy = self.arg_num(&args, 1, 0.)? as f32;
7054 let cz = self.arg_num(&args, 2, 0.)? as f32;
7055 let ux = self.arg_num(&args, 3, 1.)? as f32;
7056 let uy = self.arg_num(&args, 4, 0.)? as f32;
7057 let uz = self.arg_num(&args, 5, 0.)? as f32;
7058 let vx = self.arg_num(&args, 6, 0.)? as f32;
7059 let vy = self.arg_num(&args, 7, 0.)? as f32;
7060 let vz = self.arg_num(&args, 8, 1.)? as f32;
7061 let nt = self.arg_num(&args, 9, 5.)? as usize;
7062 let bw = self.arg_num(&args, 10, 2.)? as f32;
7063 let th = self.arg_num(&args, 11, 1.)? as f32;
7064 let tp = self.arg_num(&args, 12, 0.72)? as f32;
7065 let eo = self.arg_num(&args, 13, 0.28)? as f32;
7066 let fr = self.arg_num(&args, 14, 0.)? as f32;
7067 let hue = self.arg_num(&args, 15, 0.)? as f32;
7068 let mut gfx = self.gfx.borrow_mut();
7069 let cam = gfx.camera.clone();
7070 crate::gfx::vtex::draw_pagoda(
7071 &mut gfx.depth_queue,
7072 &cam,
7073 cx,
7074 cy,
7075 cz,
7076 ux,
7077 uy,
7078 uz,
7079 vx,
7080 vy,
7081 vz,
7082 nt,
7083 bw,
7084 th,
7085 tp,
7086 eo,
7087 fr,
7088 hue,
7089 );
7090 return Ok(Value::Unit);
7091 },
7092
7093 #[cfg(not(target_arch = "wasm32"))]
7099 "audio_tone"
7100 | "เสียงโทน"
7101 | "音调"
7102 | "音調"
7103 | "음조"
7104 | "空间音"
7105 | "空間音"
7106 | "공간음" => {
7107 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7108 let x = self.arg_num(&args, 1, 0.0)? as f32;
7109 let y = self.arg_num(&args, 2, 0.0)? as f32;
7110 let z = self.arg_num(&args, 3, 0.0)? as f32;
7111 let w = self.arg_num(&args, 4, 1.0)? as f32;
7112 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7113 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7114 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7115 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7116 if let Some(audio) = &self.audio {
7117 audio.set_tone(
7118 idx,
7119 ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth },
7120 );
7121 }
7122 return Ok(Value::Unit);
7123 },
7124
7125 #[cfg(not(target_arch = "wasm32"))]
7126 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" =>
7127 {
7128 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7129 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7130 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7131 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7132 if let Some(audio) = &self.audio {
7133 audio.set_listener(cry, sry, crx, srx);
7134 }
7135 return Ok(Value::Unit);
7136 },
7137
7138 #[cfg(not(target_arch = "wasm32"))]
7139 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" =>
7140 {
7141 let path = match args.first() {
7142 Some(Value::Str(s)) => s.clone(),
7143 _ => return Ok(Value::Unit),
7144 };
7145 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7146 if let Some(audio) = &self.audio {
7147 audio.load_bgm(&path, vol);
7148 }
7149 return Ok(Value::Unit);
7150 },
7151
7152 #[cfg(not(target_arch = "wasm32"))]
7153 "audio_bgm_volume"
7154 | "ระดับเสียงพื้นหลัง"
7155 | "ระดับเพลงประกอบ"
7156 | "背景乐音量"
7157 | "BGM音量"
7158 | "배경음악음량" => {
7159 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7160 if let Some(audio) = &self.audio {
7161 audio.set_bgm_volume(vol);
7162 }
7163 return Ok(Value::Unit);
7164 },
7165
7166 #[cfg(not(target_arch = "wasm32"))]
7167 "audio_volume" | "ระดับเสียง" | "音量" | "음량" => {
7168 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7169 if let Some(audio) = &self.audio {
7170 audio.set_master_volume(vol);
7171 }
7172 return Ok(Value::Unit);
7173 },
7174
7175 #[cfg(target_arch = "wasm32")]
7177 "audio_tone"
7178 | "เสียงโทน"
7179 | "音调"
7180 | "音調"
7181 | "음조"
7182 | "空间音"
7183 | "空間音"
7184 | "공간음" => {
7185 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7186 let x = self.arg_num(&args, 1, 0.0)? as f32;
7187 let y = self.arg_num(&args, 2, 0.0)? as f32;
7188 let z = self.arg_num(&args, 3, 0.0)? as f32;
7189 let w = self.arg_num(&args, 4, 1.0)? as f32;
7190 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7191 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7192 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7193 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7194 crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
7195 return Ok(Value::Unit);
7196 },
7197
7198 #[cfg(target_arch = "wasm32")]
7199 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" =>
7200 {
7201 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7202 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7203 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7204 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7205 crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
7206 return Ok(Value::Unit);
7207 },
7208
7209 #[cfg(target_arch = "wasm32")]
7210 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" =>
7211 {
7212 let path = self.arg_str(&args, 0, "");
7213 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7214 crate::gfx::audio_web::load_bgm(&path, vol);
7215 return Ok(Value::Unit);
7216 },
7217
7218 #[cfg(target_arch = "wasm32")]
7219 "audio_bgm_volume"
7220 | "ระดับเสียงพื้นหลัง"
7221 | "ระดับเพลงประกอบ"
7222 | "背景乐音量"
7223 | "BGM音量"
7224 | "배경음악음량" => {
7225 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7226 crate::gfx::audio_web::set_bgm_volume(vol);
7227 return Ok(Value::Unit);
7228 },
7229
7230 #[cfg(target_arch = "wasm32")]
7231 "audio_volume" | "ระดับเสียง" | "音量" | "음량" => {
7232 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7233 crate::gfx::audio_web::set_master_volume(vol);
7234 return Ok(Value::Unit);
7235 },
7236
7237 #[cfg(target_arch = "wasm32")]
7239 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" =>
7240 {
7241 let path = self.arg_str(&args, 0, "");
7242 let resolved = self.wasm_resolve_source_path(&path);
7243 match wasm_fetch_bytes(&resolved)
7244 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
7245 {
7246 Ok(t) => {
7247 let id = crate::gfx::audio_web::add_sample(&t.stereo, t.channels, t.rate);
7248 return Ok(Value::Number(id as f64));
7249 },
7250 Err(e) => {
7251 eprintln!("audio_sample_load failed ({path}): {e}");
7252 return Ok(Value::Number(-1.0));
7253 },
7254 }
7255 },
7256 #[cfg(target_arch = "wasm32")]
7257 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" =>
7258 {
7259 let id = self.arg_num(&args, 0, 0.0)? as usize;
7260 let x = self.arg_num(&args, 1, 0.0)? as f32;
7261 let y = self.arg_num(&args, 2, 0.0)? as f32;
7262 let z = self.arg_num(&args, 3, 0.0)? as f32;
7263 let vol = self.arg_num(&args, 5, 1.0)? as f32;
7265 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
7266 crate::gfx::audio_web::play_sample(id, x, y, z, vol, looping);
7267 return Ok(Value::Number(0.0));
7268 },
7269 #[cfg(target_arch = "wasm32")]
7270 "audio_sample_stop"
7271 | "停止采样"
7272 | "サンプル停止"
7273 | "샘플정지"
7274 | "หยุดตัวอย่างเสียง"
7275 | "audio_fx_reverb"
7276 | "混响"
7277 | "リバーブ"
7278 | "리버브"
7279 | "เสียงก้อง"
7280 | "audio_fx_delay"
7281 | "回声"
7282 | "ディレイ効果"
7283 | "딜레이"
7284 | "เสียงสะท้อน"
7285 | "audio_fx_lowpass"
7286 | "低通滤波"
7287 | "ローパス"
7288 | "저역통과"
7289 | "กรองความถี่ต่ำ" => {
7290 return Ok(Value::Unit);
7291 },
7292
7293 "รอหน้าต่าง" | "wait_window" | "gfx_wait" => {
7295 #[cfg(not(target_arch = "wasm32"))]
7296 loop {
7297 let still_open = {
7298 let gfx = self.gfx.borrow();
7299 gfx.window
7300 .as_ref()
7301 .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
7302 .unwrap_or(false)
7303 };
7304 if !still_open {
7305 break;
7306 }
7307 let (buf, w, h) = {
7308 let gfx = self.gfx.borrow();
7309 (gfx.buffer.clone(), gfx.width, gfx.height)
7310 };
7311 let mut gfx = self.gfx.borrow_mut();
7312 if let Some(win) = gfx.window.as_mut() {
7313 if win.update_with_buffer(&buf, w, h).is_err() {
7314 break;
7315 }
7316 }
7317 }
7318 return Ok(Value::Unit);
7319 },
7320
7321 "read_file" | "อ่านไฟล์" => {
7323 #[cfg(target_arch = "wasm32")]
7324 return Ok(Value::Str(String::new()));
7325 #[cfg(not(target_arch = "wasm32"))]
7326 {
7327 let path = self.arg_str(&args, 0, "");
7328 return std::fs::read_to_string(&path)
7329 .map(Value::Str)
7330 .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
7331 }
7332 },
7333 #[cfg(not(target_arch = "wasm32"))]
7335 "net_host" | "เน็ตโฮสต์" => {
7336 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7337 net::host(port);
7338 return Ok(Value::Unit);
7339 },
7340 #[cfg(not(target_arch = "wasm32"))]
7341 "net_join" | "เน็ตจอย" => {
7342 let ip = self.arg_str(&args, 0, "127.0.0.1");
7343 let port = self.arg_num(&args, 1, 7777.0)? as u16;
7344 net::join(&ip, port);
7345 return Ok(Value::Unit);
7346 },
7347 #[cfg(not(target_arch = "wasm32"))]
7348 "net_send" | "เน็ตส่ง" => {
7349 let s = self.arg_str(&args, 0, "");
7350 net::send(&s);
7351 return Ok(Value::Unit);
7352 },
7353 #[cfg(not(target_arch = "wasm32"))]
7354 "net_recv" | "เน็ตรับ" => {
7355 return Ok(Value::Str(net::recv()));
7356 },
7357 #[cfg(not(target_arch = "wasm32"))]
7358 "net_status" | "เน็ตสถานะ" => {
7359 return Ok(Value::Number(net::status() as f64));
7360 },
7361 #[cfg(not(target_arch = "wasm32"))]
7362 "net_recv_from" => {
7363 return Ok(Value::Str(net::recv_from()));
7364 },
7365 #[cfg(not(target_arch = "wasm32"))]
7366 "net_send_to" => {
7367 let id = self.arg_num(&args, 0, 0.0)? as u64;
7368 let s = self.arg_str(&args, 1, "");
7369 net::send_to(id, &s);
7370 return Ok(Value::Unit);
7371 },
7372 #[cfg(not(target_arch = "wasm32"))]
7373 "net_close" | "연결종료" => {
7374 net::close();
7375 return Ok(Value::Unit);
7376 },
7377 #[cfg(not(target_arch = "wasm32"))]
7379 "net_announce" | "เน็ตประกาศ" => {
7380 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7381 let info = self.arg_str(&args, 1, "");
7382 net::announce(port, &info);
7383 return Ok(Value::Unit);
7384 },
7385 #[cfg(not(target_arch = "wasm32"))]
7386 "net_announce_stop" | "เน็ตหยุดประกาศ" => {
7387 net::announce_stop();
7388 return Ok(Value::Unit);
7389 },
7390 #[cfg(not(target_arch = "wasm32"))]
7391 "net_discover" | "เน็ตค้นหา" => {
7392 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7393 return Ok(Value::Str(net::discover(port)));
7394 },
7395 #[cfg(not(target_arch = "wasm32"))]
7396 "net_test" | "เน็ตทดสอบ" => {
7397 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7398 return Ok(Value::Str(net::test_bind(port)));
7399 },
7400 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7402 "http_route" | "เว็บเส้นทาง" => {
7403 let method = self.arg_str(&args, 0, "GET").to_uppercase();
7404 let path = self.arg_str(&args, 1, "/");
7405 let handler = args.get(2).cloned().unwrap_or(Value::Unit);
7406 self.http_routes.push((method, path, handler));
7407 return Ok(Value::Unit);
7408 },
7409 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7413 "http_static" | "เว็บสแตติก" => {
7414 let prefix = self.arg_str(&args, 0, "/static");
7415 let dir = self.arg_str(&args, 1, "static");
7416 self.http_static_dirs.push((prefix, dir));
7417 return Ok(Value::Unit);
7418 },
7419 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7420 "http_serve" | "เว็บเสิร์ฟ" => {
7421 let host = self.arg_str(&args, 0, "127.0.0.1");
7422 let port = self.arg_num(&args, 1, 8080.0)? as u16;
7423 let routes = std::mem::take(&mut self.http_routes);
7424 let static_dirs = std::mem::take(&mut self.http_static_dirs);
7425 let rx = web::spawn_server(host.clone(), port, static_dirs);
7431 for pending in rx {
7432 let matched = routes
7433 .iter()
7434 .find(|(m, p, _)| m == &pending.method && p == &pending.path);
7435 let response = match matched {
7436 Some((_, _, handler)) => {
7437 let req_value = Value::Struct {
7438 name: "Request".to_string(),
7439 fields: vec![
7440 ("method".to_string(), Value::Str(pending.method.clone())),
7441 ("path".to_string(), Value::Str(pending.path.clone())),
7442 ("query".to_string(), Value::Str(pending.query.clone())),
7443 ("body".to_string(), Value::Str(pending.body.clone())),
7444 ("cookie".to_string(), Value::Str(pending.cookie.clone())),
7445 (
7446 "authorization".to_string(),
7447 Value::Str(pending.authorization.clone()),
7448 ),
7449 (
7450 "client_ip".to_string(),
7451 Value::Str(pending.client_ip.clone()),
7452 ),
7453 ],
7454 };
7455 match self.call_value(handler.clone(), vec![req_value]) {
7456 Ok(v) => web::value_to_response(&v),
7457 Err(e) => web::HttpResponse {
7458 status: 500,
7459 content_type: "text/plain; charset=utf-8".to_string(),
7460 body: format!("handler error: {e:?}"),
7461 set_cookie: None,
7462 location: None,
7463 },
7464 }
7465 },
7466 None => web::HttpResponse {
7467 status: 404,
7468 content_type: "text/plain; charset=utf-8".to_string(),
7469 body: "not found".to_string(),
7470 set_cookie: None,
7471 location: None,
7472 },
7473 };
7474 let _ = pending.respond_to.send(response);
7475 }
7476 return Ok(Value::Unit);
7477 },
7478 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7482 "http_post_async" | "เว็บโพสต์ไม่บล็อก" => {
7483 let url = self.arg_str(&args, 0, "");
7484 let body = self.arg_str(&args, 1, "");
7485 let content_type = self.arg_str(&args, 2, "application/json");
7486 let id = self.async_jobs.start_post(url, content_type, body);
7487 return Ok(Value::Str(id));
7488 },
7489 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7493 "http_job_poll" | "เว็บงานสำรวจ" => {
7494 let id = self.arg_str(&args, 0, "");
7495 return Ok(Value::Str(self.async_jobs.poll(&id).unwrap_or_default()));
7496 },
7497 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7504 "sdai_generate_start" | "เอสดีเอไอเริ่มสร้าง" => {
7505 let base_url = self.arg_str(&args, 0, "http://127.0.0.1:1342");
7506 let prompt = self.arg_str(&args, 1, "");
7507 let width = self.arg_num(&args, 2, 512.0)? as u32;
7508 let height = self.arg_num(&args, 3, 512.0)? as u32;
7509 let id = self.async_jobs.start_sdai_txt2img(base_url, prompt, width, height);
7510 return Ok(Value::Str(id));
7511 },
7512 "query_param" | "พารามิเตอร์" => {
7514 let qs = self.arg_str(&args, 0, "");
7515 let name = self.arg_str(&args, 1, "");
7516 let default = self.arg_str(&args, 2, "");
7517 let mut found = default;
7518 for pair in qs.split('&') {
7519 let mut it = pair.splitn(2, '=');
7520 if it.next().unwrap_or("") == name {
7521 found = url_decode(it.next().unwrap_or(""));
7522 break;
7523 }
7524 }
7525 return Ok(Value::Str(found));
7526 },
7527 "cookie_get" | "รับคุกกี้" => {
7529 let header = self.arg_str(&args, 0, "");
7530 let name = self.arg_str(&args, 1, "");
7531 let default = self.arg_str(&args, 2, "");
7532 let mut found = default;
7533 for pair in header.split(';') {
7534 let p = pair.trim();
7535 let mut it = p.splitn(2, '=');
7536 if it.next().unwrap_or("") == name {
7537 found = it.next().unwrap_or("").to_string();
7538 break;
7539 }
7540 }
7541 return Ok(Value::Str(found));
7542 },
7543 "html_escape" | "กันเอชทีเอ็มแอล" => {
7545 let s = self.arg_str(&args, 0, "");
7546 return Ok(Value::Str(
7547 s.replace('&', "&")
7548 .replace('<', "<")
7549 .replace('>', ">")
7550 .replace('"', """)
7551 .replace('\'', "'"),
7552 ));
7553 },
7554 "json_escape" | "หนีเจสัน" => {
7559 let s = self.arg_str(&args, 0, "");
7560 let mut out = String::with_capacity(s.len() + 8);
7561 for c in s.chars() {
7562 match c {
7563 '"' => out.push_str("\\\""),
7564 '\\' => out.push_str("\\\\"),
7565 '\n' => out.push_str("\\n"),
7566 '\r' => out.push_str("\\r"),
7567 '\t' => out.push_str("\\t"),
7568 c if (c as u32) < 0x20 => {
7569 out.push_str(&format!("\\u{:04x}", c as u32))
7570 },
7571 c => out.push(c),
7572 }
7573 }
7574 return Ok(Value::Str(out));
7575 },
7576 "cli_arg" | "อาร์กิวเมนต์" => {
7578 let name = self.arg_str(&args, 0, "");
7579 let default = self.arg_str(&args, 1, "");
7580 let flag = format!("--{name}");
7581 let argv: Vec<String> = std::env::args().collect();
7582 let found = argv
7583 .iter()
7584 .position(|a| a == &flag)
7585 .and_then(|i| argv.get(i + 1))
7586 .cloned()
7587 .unwrap_or(default);
7588 return Ok(Value::Str(found));
7589 },
7590 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7592 "db_open" | "ฐานข้อมูลเปิด" => {
7593 let path = self.arg_str(&args, 0, "app.db");
7594 let conn = ling_http::rusqlite::Connection::open(&path)
7595 .map_err(|e| EvalErr::from(format!("db_open '{path}': {e}")))?;
7596 let _ = conn.execute_batch("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;");
7597 self.db = Some(conn);
7598 return Ok(Value::Unit);
7599 },
7600 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7603 "db_exec" | "ฐานข้อมูลรัน" => {
7604 let sql = self.arg_str(&args, 0, "");
7605 let params = values_to_sql_params(&args[1.min(args.len())..]);
7606 let conn = self
7607 .db
7608 .as_ref()
7609 .ok_or_else(|| EvalErr::from("db_exec: call db_open first".to_string()))?;
7610 let n = conn
7611 .execute(
7612 &sql,
7613 ling_http::rusqlite::params_from_iter(params.iter()),
7614 )
7615 .map_err(|e| EvalErr::from(format!("db_exec: {e}\n sql: {sql}")))?;
7616 return Ok(Value::Number(n as f64));
7617 },
7618 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7620 "db_query" | "ฐานข้อมูลถาม" => {
7621 let sql = self.arg_str(&args, 0, "");
7622 let params = values_to_sql_params(&args[1.min(args.len())..]);
7623 let conn = self
7624 .db
7625 .as_ref()
7626 .ok_or_else(|| EvalErr::from("db_query: call db_open first".to_string()))?;
7627 let mut stmt = conn
7628 .prepare(&sql)
7629 .map_err(|e| EvalErr::from(format!("db_query: {e}\n sql: {sql}")))?;
7630 let col_names: Vec<String> =
7631 stmt.column_names().iter().map(|s| s.to_string()).collect();
7632 let mut rows = stmt
7633 .query(ling_http::rusqlite::params_from_iter(params.iter()))
7634 .map_err(|e| EvalErr::from(format!("db_query: {e}")))?;
7635 let mut out = Vec::new();
7636 while let Some(row) = rows
7637 .next()
7638 .map_err(|e| EvalErr::from(format!("db_query row: {e}")))?
7639 {
7640 let mut fields = Vec::with_capacity(col_names.len());
7641 for (i, col) in col_names.iter().enumerate() {
7642 use ling_http::rusqlite::types::ValueRef;
7643 let v = match row.get_ref(i) {
7644 Ok(ValueRef::Null) => Value::Str(String::new()),
7645 Ok(ValueRef::Integer(n)) => Value::Number(n as f64),
7646 Ok(ValueRef::Real(n)) => Value::Number(n),
7647 Ok(ValueRef::Text(t)) => {
7648 Value::Str(String::from_utf8_lossy(t).into_owned())
7649 },
7650 Ok(ValueRef::Blob(b)) =>
7651
7652 {
7653 use base64::Engine as _;
7654 Value::Str(base64::engine::general_purpose::STANDARD.encode(b))
7655 },
7656 Err(_) => Value::Str(String::new()),
7657 };
7658 fields.push((col.clone(), v));
7659 }
7660 out.push(Value::Struct { name: "Row".to_string(), fields });
7661 }
7662 return Ok(Value::List(Rc::new(out)));
7663 },
7664 #[cfg(not(target_arch = "wasm32"))]
7666 "gamepad_poll" | "จอยโพล" => {
7667 gamepad::poll();
7668 return Ok(Value::Unit);
7669 },
7670 #[cfg(not(target_arch = "wasm32"))]
7671 "gamepad_button" | "จอยปุ่ม" => {
7672 let name = self.arg_str(&args, 0, "");
7673 return Ok(Value::Number(if gamepad::button(&name) {
7674 1.0
7675 } else {
7676 0.0
7677 }));
7678 },
7679 #[cfg(not(target_arch = "wasm32"))]
7680 "gamepad_axis" | "จอยแกน" => {
7681 let name = self.arg_str(&args, 0, "");
7682 return Ok(Value::Number(gamepad::axis(&name) as f64));
7683 },
7684 #[cfg(not(target_arch = "wasm32"))]
7685 "gamepad_rumble" | "จอยสั่น" => {
7686 let low = self.arg_num(&args, 0, 0.0)? as f32;
7687 let high = self.arg_num(&args, 1, 0.0)? as f32;
7688 let ms = self.arg_num(&args, 2, 200.0)? as u32;
7689 gamepad::rumble(low, high, ms);
7690 return Ok(Value::Unit);
7691 },
7692 #[cfg(not(target_arch = "wasm32"))]
7693 "gamepad_list" | "จอยรายการ" => {
7694 return Ok(Value::Str(gamepad::list()));
7695 },
7696 #[cfg(not(target_arch = "wasm32"))]
7697 "gamepad_any" | "จอยใดๆ" => {
7698 return Ok(Value::Number(if gamepad::any_button() { 1.0 } else { 0.0 }));
7699 },
7700 #[cfg(target_arch = "wasm32")]
7702 "gamepad_poll" | "จอยโพล" | "gamepad_rumble" | "จอยสั่น" => {
7703 return Ok(Value::Unit);
7704 },
7705 #[cfg(target_arch = "wasm32")]
7706 "gamepad_button" | "จอยปุ่ม" | "gamepad_axis" | "จอยแกน" | "gamepad_any" | "จอยใดๆ" =>
7707 {
7708 return Ok(Value::Number(0.0));
7709 },
7710 #[cfg(target_arch = "wasm32")]
7711 "gamepad_list" | "จอยรายการ" => {
7712 return Ok(Value::Str(String::new()));
7713 },
7714
7715 #[cfg(not(target_arch = "wasm32"))]
7718 "nn_new" | "建神经网" | "ニューラル作成" | "신경망생성" | "สร้างโครงข่าย" =>
7719 {
7720 let n_in = self.arg_num(&args, 0, 1.0)?.max(0.0) as usize;
7721 let seed = self.arg_num(&args, 1, 1.0)? as u64;
7722 return Ok(Value::Number(ai::nn_new(n_in, seed) as f64));
7723 },
7724 #[cfg(not(target_arch = "wasm32"))]
7726 "nn_dense" | "密集层" | "密層追加" | "밀집층" | "ชั้นหนาแน่น" =>
7727 {
7728 let id = self.arg_num(&args, 0, -1.0)? as i64;
7729 let units = self.arg_num(&args, 1, 1.0)?.max(1.0) as usize;
7730 let act = self.arg_str(&args, 2, "relu");
7731 ai::nn_dense(id, units, &act);
7732 return Ok(Value::Unit);
7733 },
7734 #[cfg(not(target_arch = "wasm32"))]
7736 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" =>
7737 {
7738 let id = self.arg_num(&args, 0, -1.0)? as i64;
7739 let input = self.arg_list_f32(&args, 1);
7740 let out = ai::nn_forward(id, &input);
7741 return Ok(Value::List(Rc::new(
7742 out.into_iter().map(|v| Value::Number(v as f64)).collect(),
7743 )));
7744 },
7745 #[cfg(not(target_arch = "wasm32"))]
7747 "nn_train" | "训练网" | "ニューラル学習" | "신경망학습" | "ฝึกโครงข่าย" =>
7748 {
7749 let id = self.arg_num(&args, 0, -1.0)? as i64;
7750 let input = self.arg_list_f32(&args, 1);
7751 let target = self.arg_list_f32(&args, 2);
7752 let lr = self.arg_num(&args, 3, 0.01)? as f32;
7753 return Ok(Value::Number(ai::nn_train(id, &input, &target, lr) as f64));
7754 },
7755 #[cfg(not(target_arch = "wasm32"))]
7757 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" =>
7758 {
7759 let id = self.arg_num(&args, 0, -1.0)? as i64;
7760 let path = self.arg_str(&args, 1, "model.lnn");
7761 return Ok(Value::Bool(ai::nn_save(id, &path)));
7762 },
7763 #[cfg(not(target_arch = "wasm32"))]
7765 "nn_load" | "载入网" | "網読込" | "신경망불러오기" | "โหลดโครงข่าย" =>
7766 {
7767 let path = self.arg_str(&args, 0, "model.lnn");
7768 return Ok(Value::Number(ai::nn_load(&path) as f64));
7769 },
7770
7771 #[cfg(not(target_arch = "wasm32"))]
7774 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" =>
7775 {
7776 let spec = self.arg_str(&args, 0, "");
7777 return Ok(Value::Number(ai::bt_build(&spec) as f64));
7778 },
7779 #[cfg(not(target_arch = "wasm32"))]
7781 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" =>
7782 {
7783 let id = self.arg_num(&args, 0, -1.0)? as i64;
7784 let key = self.arg_str(&args, 1, "");
7785 let val = self.arg_num(&args, 2, 0.0)? as f32;
7786 ai::bt_set(id, &key, val);
7787 return Ok(Value::Unit);
7788 },
7789 #[cfg(not(target_arch = "wasm32"))]
7791 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" =>
7792 {
7793 let id = self.arg_num(&args, 0, -1.0)? as i64;
7794 return Ok(Value::Str(ai::bt_tick(id)));
7795 },
7796 #[cfg(not(target_arch = "wasm32"))]
7798 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" =>
7799 {
7800 let id = self.arg_num(&args, 0, -1.0)? as i64;
7801 return Ok(Value::Number(ai::bt_status(id) as f64));
7802 },
7803
7804 #[cfg(not(target_arch = "wasm32"))]
7807 "dialog_new" | "建对话模型" | "対話モデル作成" | "대화모델생성" | "สร้างโมเดลสนทนา" =>
7808 {
7809 let ctx = self.arg_num(&args, 0, 3.0)?.max(1.0) as usize;
7810 let embed = self.arg_num(&args, 1, 32.0)?.max(1.0) as usize;
7811 let hidden = self.arg_num(&args, 2, 64.0)?.max(1.0) as usize;
7812 let seed = self.arg_num(&args, 3, 1.0)? as u64;
7813 return Ok(Value::Number(
7814 ai::dialog_new(ctx, embed, hidden, seed) as f64
7815 ));
7816 },
7817 #[cfg(not(target_arch = "wasm32"))]
7819 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" =>
7820 {
7821 let id = self.arg_num(&args, 0, -1.0)? as i64;
7822 let text = self.arg_str(&args, 1, "");
7823 ai::dialog_learn(id, &text);
7824 return Ok(Value::Unit);
7825 },
7826 #[cfg(not(target_arch = "wasm32"))]
7828 "dialog_load" | "对话载入" | "対話読込" | "대화불러오기" | "โหลดชุดสนทนา" =>
7829 {
7830 let id = self.arg_num(&args, 0, -1.0)? as i64;
7831 let path = self.arg_str(&args, 1, "");
7832 return Ok(Value::Number(ai::dialog_load(id, &path) as f64));
7833 },
7834 #[cfg(not(target_arch = "wasm32"))]
7836 "dialog_train" | "对话训练" | "対話訓練" | "대화훈련" | "ฝึกสนทนา" =>
7837 {
7838 let id = self.arg_num(&args, 0, -1.0)? as i64;
7839 let epochs = self.arg_num(&args, 1, 20.0)?.max(1.0) as usize;
7840 let lr = self.arg_num(&args, 2, 0.1)? as f32;
7841 return Ok(Value::Number(ai::dialog_train(id, epochs, lr) as f64));
7842 },
7843 #[cfg(not(target_arch = "wasm32"))]
7845 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" =>
7846 {
7847 let id = self.arg_num(&args, 0, -1.0)? as i64;
7848 let prompt = self.arg_str(&args, 1, "");
7849 let max = self.arg_num(&args, 2, 24.0)?.max(1.0) as usize;
7850 let temp = self.arg_num(&args, 3, 0.8)? as f32;
7851 return Ok(Value::Str(ai::dialog_say(id, &prompt, max, temp)));
7852 },
7853 #[cfg(not(target_arch = "wasm32"))]
7855 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" =>
7856 {
7857 let id = self.arg_num(&args, 0, -1.0)? as i64;
7858 let path = self.arg_str(&args, 1, "model.llm");
7859 return Ok(Value::Bool(ai::dialog_save(id, &path)));
7860 },
7861 #[cfg(not(target_arch = "wasm32"))]
7863 "dialog_load_model"
7864 | "对话载模"
7865 | "対話モデル読込"
7866 | "대화모델불러오기"
7867 | "โหลดโมเดลสนทนา" => {
7868 let path = self.arg_str(&args, 0, "model.llm");
7869 return Ok(Value::Number(ai::dialog_load_model(&path) as f64));
7870 },
7871
7872 "url_decode" | "网址解码" => {
7875 let s = self.arg_str(&args, 0, "");
7876 let bytes = s.as_bytes();
7877 let mut out = Vec::with_capacity(bytes.len());
7878 let mut i = 0;
7879 while i < bytes.len() {
7880 match bytes[i] {
7881 b'+' => {
7882 out.push(b' ');
7883 i += 1;
7884 },
7885 b'%' if i + 2 < bytes.len() => {
7886 let hex = std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or("");
7887 match u8::from_str_radix(hex, 16) {
7888 Ok(b) => {
7889 out.push(b);
7890 i += 3;
7891 },
7892 Err(_) => {
7893 out.push(bytes[i]);
7894 i += 1;
7895 },
7896 }
7897 },
7898 b => {
7899 out.push(b);
7900 i += 1;
7901 },
7902 }
7903 }
7904 return Ok(Value::Str(String::from_utf8_lossy(&out).into_owned()));
7905 },
7906 "now_unix" | "现在时间" => {
7910 return Ok(Value::Number(now_secs()));
7911 },
7912 "file_exists" | "文件存在" => {
7913 #[cfg(target_arch = "wasm32")]
7914 return Ok(Value::Bool(false));
7915 #[cfg(not(target_arch = "wasm32"))]
7916 {
7917 let path = self.arg_str(&args, 0, "");
7918 return Ok(Value::Bool(std::path::Path::new(&path).exists()));
7919 }
7920 },
7921 "write_file" | "เขียนไฟล์" => {
7922 #[cfg(target_arch = "wasm32")]
7923 return Ok(Value::Unit);
7924 #[cfg(not(target_arch = "wasm32"))]
7925 {
7926 let path = self.arg_str(&args, 0, "");
7927 let content = self.arg_str(&args, 1, "");
7928 std::fs::write(&path, content.as_bytes())
7929 .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
7930 return Ok(Value::Unit);
7931 }
7932 },
7933 "print_file" | "พิมพ์ไฟล์" => {
7934 let content = self.arg_str(&args, 0, "");
7935 print!("{content}");
7936 return Ok(Value::Unit);
7937 },
7938
7939 "get_args" | "รับอาร์กิวเมนต์" => {
7941 let v: Vec<Value> = std::env::args().map(Value::Str).collect();
7942 return Ok(Value::List(Rc::new(v)));
7943 },
7944
7945 #[cfg(not(target_arch = "wasm32"))]
7950 "list_dir" | "รายการไดเรกทอรี" => {
7951 let path = self.arg_str(&args, 0, ".");
7952 let mut paths: Vec<String> = Vec::new();
7953 if let Ok(rd) = std::fs::read_dir(&path) {
7954 for e in rd.flatten() {
7955 paths.push(e.path().to_string_lossy().into_owned());
7956 }
7957 }
7958 paths.sort();
7959 let out: Vec<Value> = paths.into_iter().map(Value::Str).collect();
7960 return Ok(Value::List(Rc::new(out)));
7961 },
7962 #[cfg(not(target_arch = "wasm32"))]
7963 "is_dir" | "เป็นไดเรกทอรี" => {
7964 let path = self.arg_str(&args, 0, "");
7965 return Ok(Value::Bool(std::path::Path::new(&path).is_dir()));
7966 },
7967 #[cfg(not(target_arch = "wasm32"))]
7968 "is_file" | "เป็นไฟล์" => {
7969 let path = self.arg_str(&args, 0, "");
7970 return Ok(Value::Bool(std::path::Path::new(&path).is_file()));
7971 },
7972 #[cfg(not(target_arch = "wasm32"))]
7973 "path_name" | "ชื่อไฟล์" => {
7974 let path = self.arg_str(&args, 0, "");
7975 let name = std::path::Path::new(&path)
7976 .file_name()
7977 .map(|s| s.to_string_lossy().into_owned())
7978 .unwrap_or_default();
7979 return Ok(Value::Str(name));
7980 },
7981 #[cfg(not(target_arch = "wasm32"))]
7982 "path_ext" | "นามสกุลไฟล์" => {
7983 let path = self.arg_str(&args, 0, "");
7984 let ext = std::path::Path::new(&path)
7985 .extension()
7986 .map(|s| s.to_string_lossy().to_lowercase())
7987 .unwrap_or_default();
7988 return Ok(Value::Str(ext));
7989 },
7990 #[cfg(not(target_arch = "wasm32"))]
7991 "file_size" | "ขนาดไฟล์" => {
7992 let path = self.arg_str(&args, 0, "");
7993 let sz = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
7994 return Ok(Value::Number(sz as f64));
7995 },
7996 #[cfg(not(target_arch = "wasm32"))]
7997 "file_modified" | "เวลาที่แก้ไข" => {
7998 let path = self.arg_str(&args, 0, "");
7999 let secs = std::fs::metadata(&path)
8000 .and_then(|m| m.modified())
8001 .ok()
8002 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8003 .map(|d| d.as_secs_f64())
8004 .unwrap_or(0.0);
8005 return Ok(Value::Number(secs));
8006 },
8007 #[cfg(not(target_arch = "wasm32"))]
8008 "file_created" | "เวลาที่สร้าง" => {
8009 let path = self.arg_str(&args, 0, "");
8010 let secs = std::fs::metadata(&path)
8011 .ok()
8012 .and_then(|m| m.created().or_else(|_| m.modified()).ok())
8013 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8014 .map(|d| d.as_secs_f64())
8015 .unwrap_or(0.0);
8016 return Ok(Value::Number(secs));
8017 },
8018 #[cfg(not(target_arch = "wasm32"))]
8019 "make_dir" | "สร้างไดเรกทอรี" => {
8020 let path = self.arg_str(&args, 0, "");
8021 return Ok(Value::Bool(std::fs::create_dir_all(&path).is_ok()));
8022 },
8023 "str_strip_prefix" | "ตัดคำนำหน้า" => {
8025 let s = self.arg_str(&args, 0, "");
8026 let prefix = self.arg_str(&args, 1, "");
8027 return Ok(Value::Str(
8028 s.strip_prefix(&prefix).unwrap_or(&s).to_string(),
8029 ));
8030 },
8031 #[cfg(not(target_arch = "wasm32"))]
8034 "file_magic" | "มายาไฟล์" => {
8035 let path = self.arg_str(&args, 0, "");
8036 let kind = match std::fs::File::open(&path) {
8037 Ok(mut f) => {
8038 use std::io::Read;
8039 let mut buf = [0u8; 4];
8040 let n = f.read(&mut buf).unwrap_or(0);
8041 if n >= 2 && buf[0] == 0x1f && buf[1] == 0x8b {
8042 "gzip"
8043 } else if n >= 4 && &buf[0..2] == b"PK" {
8044 "zip"
8045 } else {
8046 "other"
8047 }
8048 },
8049 Err(_) => "missing",
8050 };
8051 return Ok(Value::Str(kind.to_string()));
8052 },
8053 #[cfg(not(target_arch = "wasm32"))]
8055 "copy_file" | "คัดลอกไฟล์" => {
8056 let src = self.arg_str(&args, 0, "");
8057 let dst = self.arg_str(&args, 1, "");
8058 if let Some(parent) = std::path::Path::new(&dst).parent() {
8059 let _ = std::fs::create_dir_all(parent);
8060 }
8061 return Ok(Value::Bool(std::fs::copy(&src, &dst).is_ok()));
8062 },
8063 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8067 "tar_gz_list" | "รายการทาร์" => {
8068 let path = self.arg_str(&args, 0, "");
8069 let mut names: Vec<String> = Vec::new();
8070 if let Ok(file) = std::fs::File::open(&path) {
8071 let gz = flate2::read::GzDecoder::new(file);
8072 let mut ar = tar::Archive::new(gz);
8073 if let Ok(entries) = ar.entries() {
8074 for entry in entries.flatten() {
8075 if entry.header().entry_type().is_file() {
8076 if let Ok(p) = entry.path() {
8077 names.push(
8078 p.to_string_lossy()
8079 .trim_start_matches("./")
8080 .replace('\\', "/"),
8081 );
8082 }
8083 }
8084 }
8085 }
8086 }
8087 names.sort();
8088 names.dedup();
8089 let out: Vec<Value> = names.into_iter().map(Value::Str).collect();
8090 return Ok(Value::List(Rc::new(out)));
8091 },
8092 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8096 "tar_gz_read" | "อ่านทาร์" => {
8097 let path = self.arg_str(&args, 0, "");
8098 let want = self.arg_str(&args, 1, "");
8099 let want = want.trim_start_matches("./").replace('\\', "/");
8100 let mut content = String::new();
8101 if let Ok(file) = std::fs::File::open(&path) {
8102 let gz = flate2::read::GzDecoder::new(file);
8103 let mut ar = tar::Archive::new(gz);
8104 if let Ok(entries) = ar.entries() {
8105 for entry in entries.flatten() {
8106 let mut entry = entry;
8107 let name = match entry.path() {
8108 Ok(p) => p
8109 .to_string_lossy()
8110 .trim_start_matches("./")
8111 .replace('\\', "/"),
8112 Err(_) => continue,
8113 };
8114 if name == want {
8115 use std::io::Read;
8116 let mut buf = Vec::new();
8117 let cap = 256 * 1024;
8118 if entry.take(cap as u64 + 1).read_to_end(&mut buf).is_ok() {
8119 let slice = if buf.len() > cap { &buf[..cap] } else { &buf[..] };
8120 content = String::from_utf8_lossy(slice).into_owned();
8121 }
8122 break;
8123 }
8124 }
8125 }
8126 }
8127 return Ok(Value::Str(content));
8128 },
8129 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8136 "totp_secret" | "โทเทนลับ" => {
8137 let mut bytes = [0u8; 20];
8138 rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut bytes);
8139 return Ok(Value::Str(base32_encode(&bytes)));
8140 },
8141 "totp_uri" | "โทเทนยูอาร์ไอ" => {
8145 let secret = self.arg_str(&args, 0, "");
8146 let account = self.arg_str(&args, 1, "user");
8147 let issuer = self.arg_str(&args, 2, "lingfu");
8148 return Ok(Value::Str(format!(
8149 "otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}&algorithm=SHA1&digits=6&period=30"
8150 )));
8151 },
8152 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8154 "totp_verify" | "โทเทนตรวจ" => {
8155 let secret = self.arg_str(&args, 0, "");
8156 let code = self.arg_str(&args, 1, "");
8157 let ok = totp_check(&secret, code.trim());
8158 return Ok(Value::Bool(ok));
8159 },
8160 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8162 "totp_now" | "โทเทนตอนนี้" => {
8163 let secret = self.arg_str(&args, 0, "");
8164 let step = (crate::runtime::now_secs() as u64) / 30;
8165 return Ok(Value::Str(
8166 totp_code(&secret, step).unwrap_or_default(),
8167 ));
8168 },
8169 #[cfg(not(target_arch = "wasm32"))]
8171 "file_hash" | "แฮชไฟล์" => {
8172 let path = self.arg_str(&args, 0, "");
8173 match std::fs::read(&path) {
8174 Ok(bytes) => {
8175 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(&bytes))))
8176 },
8177 Err(_) => return Ok(Value::Str(String::new())),
8178 }
8179 },
8180 "hash_hex" | "แฮชสตริง" => {
8182 let s = self.arg_str(&args, 0, "");
8183 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(
8184 s.as_bytes(),
8185 ))));
8186 },
8187 #[cfg(not(target_arch = "wasm32"))]
8189 "env_get" | "รับตัวแปรแวดล้อม" => {
8190 let name = self.arg_str(&args, 0, "");
8191 let dflt = self.arg_str(&args, 1, "");
8192 return Ok(Value::Str(std::env::var(&name).unwrap_or(dflt)));
8193 },
8194
8195 "to_number" | "转数字" => {
8199 let s = self.arg_str(&args, 0, "");
8200 return Ok(Value::Number(s.trim().parse().unwrap_or(0.0)));
8201 },
8202 "sha256_hex" | "SHA256哈希" => {
8207 use sha2::Digest;
8208 let s = self.arg_str(&args, 0, "");
8209 let mut h = sha2::Sha256::new();
8210 h.update(s.as_bytes());
8211 return Ok(Value::Str(hex_encode(&h.finalize())));
8212 },
8213 "hex_to_number" | "十六进制转数字" => {
8216 let s = self.arg_str(&args, 0, "");
8217 let v = u64::from_str_radix(s.trim(), 16).unwrap_or(0);
8218 return Ok(Value::Number(v as f64));
8219 },
8220 "split" | "str_split" | "แยก" => {
8221 let s = self.arg_str(&args, 0, "");
8222 let sep = self.arg_str(&args, 1, "\n");
8223 let sep = if sep.is_empty() { "\n".into() } else { sep };
8224 let parts: Vec<Value> = s
8225 .split(sep.as_str())
8226 .map(|p| Value::Str(p.to_string()))
8227 .collect();
8228 return Ok(Value::List(Rc::new(parts)));
8229 },
8230 "trim" | "str_trim" | "ตัดช่องว่าง" => {
8231 let s = self.arg_str(&args, 0, "");
8232 return Ok(Value::Str(s.trim().to_string()));
8233 },
8234 "starts_with" | "str_starts_with" | "เริ่มด้วย" => {
8235 let s = self.arg_str(&args, 0, "");
8236 let prefix = self.arg_str(&args, 1, "");
8237 return Ok(Value::Bool(s.starts_with(prefix.as_str())));
8238 },
8239 "ends_with" | "str_ends_with" | "ลงท้ายด้วย" => {
8240 let s = self.arg_str(&args, 0, "");
8241 let suffix = self.arg_str(&args, 1, "");
8242 return Ok(Value::Bool(s.ends_with(suffix.as_str())));
8243 },
8244 "str_replace" | "แทนสตริง" => {
8245 let s = self.arg_str(&args, 0, "");
8246 let from = self.arg_str(&args, 1, "");
8247 let to = self.arg_str(&args, 2, "");
8248 return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
8249 },
8250 "str_find" | "หาในสตริง" => {
8251 let s = self.arg_str(&args, 0, "");
8252 let needle = self.arg_str(&args, 1, "");
8253 let pos = s
8255 .find(needle.as_str())
8256 .map(|byte_i| s[..byte_i].chars().count() as f64)
8257 .unwrap_or(-1.0);
8258 return Ok(Value::Number(pos));
8259 },
8260 "substr" | "str_slice" | "ส่วนสตริง" => {
8261 let s = self.arg_str(&args, 0, "");
8262 let start = self.arg_num(&args, 1, 0.0)? as usize;
8263 let len = args
8264 .get(2)
8265 .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
8266 .unwrap_or_else(|| s.chars().count().saturating_sub(start));
8267 let chars: Vec<char> = s.chars().collect();
8268 let end = (start + len).min(chars.len());
8269 let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
8270 return Ok(Value::Str(slice));
8271 },
8272 "to_str" | "str" | "num_str" | "แปลงสตริง" => {
8273 let v = args.into_iter().next().unwrap_or(Value::Unit);
8274 return Ok(Value::Str(v.to_string()));
8275 },
8276 "str_repeat" | "ทำซ้ำสตริง" => {
8277 let s = self.arg_str(&args, 0, "");
8278 let n = self.arg_num(&args, 1, 1.0)? as usize;
8279 return Ok(Value::Str(s.repeat(n)));
8280 },
8281 "str_upper" => {
8282 let s = self.arg_str(&args, 0, "");
8283 return Ok(Value::Str(s.to_uppercase()));
8284 },
8285 "str_lower" => {
8286 let s = self.arg_str(&args, 0, "");
8287 return Ok(Value::Str(s.to_lowercase()));
8288 },
8289 "str_len" | "len" | "ความยาว" | "长度" | "長さ" | "길이" => {
8290 match args.first() {
8291 Some(Value::Str(s)) => return Ok(Value::Number(s.chars().count() as f64)),
8292 Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
8293 _ => return Ok(Value::Number(0.0)),
8294 }
8295 },
8296
8297 "hash_str" | "แฮช" => {
8299 let s = self.arg_str(&args, 0, "");
8300 let mut h: u64 = 14695981039346656037_u64;
8301 for b in s.bytes() {
8302 h ^= b as u64;
8303 h = h.wrapping_mul(1099511628211);
8304 }
8305 return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
8306 },
8307 "hash_int" | "แฮชจำนวน" => {
8308 let s = self.arg_str(&args, 0, "");
8309 let n = self.arg_num(&args, 1, 100.0)? as u64;
8310 let mut h: u64 = 14695981039346656037_u64;
8311 for b in s.bytes() {
8312 h ^= b as u64;
8313 h = h.wrapping_mul(1099511628211);
8314 }
8315 return Ok(Value::Number((h % n.max(1)) as f64));
8316 },
8317
8318 "list_new" | "รายการใหม่" | "新建列表" | "新規リスト" | "새목록" =>
8320 {
8321 return Ok(Value::List(Rc::new(Vec::new())));
8322 },
8323 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가" =>
8324 {
8325 let lst = args
8326 .first()
8327 .cloned()
8328 .unwrap_or(Value::List(Rc::new(vec![])));
8329 let val = args.get(1).cloned().unwrap_or(Value::Unit);
8330 if let Value::List(mut v) = lst {
8331 Rc::make_mut(&mut v).push(val);
8332 return Ok(Value::List(v));
8333 }
8334 return Ok(Value::List(Rc::new(vec![val])));
8335 },
8336 "list_get" | "รับรายการ" | "取元素" | "要素取得" | "요소가져오기" =>
8337 {
8338 let i = self.arg_num(&args, 1, 0.0)? as usize;
8340 if let Some(Value::List(v)) = args.first() {
8341 return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
8342 }
8343 return Ok(Value::Str(String::new()));
8344 },
8345 "list_max" | "列表最大值" => {
8349 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8350 let default = self.arg_num(&args, 1, 0.0)?;
8351 if let Value::List(v) = lst {
8352 let mut best = default;
8353 let mut any = false;
8354 for item in v.iter() {
8355 if let Value::Number(n) = item {
8356 if !any || *n > best {
8357 best = *n;
8358 any = true;
8359 }
8360 }
8361 }
8362 return Ok(Value::Number(best));
8363 }
8364 return Ok(Value::Number(default));
8365 },
8366 "list_min" | "列表最小值" => {
8367 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8368 let default = self.arg_num(&args, 1, 0.0)?;
8369 if let Value::List(v) = lst {
8370 let mut best = default;
8371 let mut any = false;
8372 for item in v.iter() {
8373 if let Value::Number(n) = item {
8374 if !any || *n < best {
8375 best = *n;
8376 any = true;
8377 }
8378 }
8379 }
8380 return Ok(Value::Number(best));
8381 }
8382 return Ok(Value::Number(default));
8383 },
8384 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정" =>
8388 {
8389 let idx = self.arg_num(&args, 1, 0.0)? as usize;
8390 let mut ai = args.into_iter();
8391 let lst = ai.next().unwrap_or(Value::List(Rc::new(vec![])));
8392 ai.next(); let val = ai.next().unwrap_or(Value::Unit);
8394 if let Value::List(mut v) = lst {
8395 if idx < v.len() {
8396 Rc::make_mut(&mut v)[idx] = val;
8397 }
8398 return Ok(Value::List(v));
8399 }
8400 return Ok(Value::List(Rc::new(vec![])));
8401 },
8402 "list_join" | "join" | "รวมรายการ" | "连接" | "連結" | "연결" =>
8403 {
8404 let lst = args
8405 .first()
8406 .cloned()
8407 .unwrap_or(Value::List(Rc::new(vec![])));
8408 let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8409 if let Value::List(v) = lst {
8410 return Ok(Value::Str(
8411 v.iter()
8412 .map(|x| x.to_string())
8413 .collect::<Vec<_>>()
8414 .join(&sep),
8415 ));
8416 }
8417 return Ok(Value::Str(String::new()));
8418 },
8419 "list_map" | "映射列表" => {
8428 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8429 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8430 if let Value::List(v) = lst {
8431 let mut out = Vec::with_capacity(v.len());
8432 for item in v.iter() {
8433 out.push(self.call_value(f.clone(), vec![item.clone()])?);
8434 }
8435 return Ok(Value::List(Rc::new(out)));
8436 }
8437 return Ok(Value::List(Rc::new(vec![])));
8438 },
8439 "list_filter" | "过滤列表" => {
8440 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8441 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8442 if let Value::List(v) = lst {
8443 let mut out = Vec::new();
8444 for item in v.iter() {
8445 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8446 out.push(item.clone());
8447 }
8448 }
8449 return Ok(Value::List(Rc::new(out)));
8450 }
8451 return Ok(Value::List(Rc::new(vec![])));
8452 },
8453 "list_find" | "查找列表" => {
8455 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8456 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8457 if let Value::List(v) = lst {
8458 for item in v.iter() {
8459 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8460 return Ok(item.clone());
8461 }
8462 }
8463 }
8464 return Ok(Value::Unit);
8465 },
8466 #[cfg(not(target_arch = "wasm32"))]
8470 "blob_f32" | "blob_i32" => {
8471 let s = self.arg_str(&args, 0, "");
8472 let is_i32 = name == "blob_i32";
8473 match decode_blob(&s) {
8474 Ok(bytes) => {
8475 let mut out = Vec::with_capacity(bytes.len() / 4);
8476 for ch in bytes.chunks_exact(4) {
8477 let arr = [ch[0], ch[1], ch[2], ch[3]];
8478 let n = if is_i32 {
8479 i32::from_le_bytes(arr) as f64
8480 } else {
8481 f32::from_le_bytes(arr) as f64
8482 };
8483 out.push(Value::Number(n));
8484 }
8485 return Ok(Value::List(Rc::new(out)));
8486 },
8487 Err(e) => {
8488 eprintln!("blob decode failed: {e}");
8489 return Ok(Value::List(Rc::new(vec![])));
8490 },
8491 }
8492 },
8493
8494 "svg_begin" | "开始SVG" | "เริ่มSVG" => {
8501 let path = self.arg_str(&args, 0, "output.svg");
8502 let width = self.arg_num(&args, 1, 800.0)?;
8503 let height = self.arg_num(&args, 2, 600.0)?;
8504 *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
8505 return Ok(Value::Unit);
8506 },
8507
8508 "svg_rect" | "SVG矩形" | "SVGสี่เหลี่ยม" => {
8509 let x = self.arg_num(&args, 0, 0.0)?;
8510 let y = self.arg_num(&args, 1, 0.0)?;
8511 let w = self.arg_num(&args, 2, 10.0)?;
8512 let h = self.arg_num(&args, 3, 10.0)?;
8513 let fill = self.arg_str(&args, 4, "#ffffff");
8514 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8515 svg.elements.push(format!(
8516 "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
8517 height=\"{h:.1}\" fill=\"{fill}\"/>"
8518 ));
8519 }
8520 return Ok(Value::Unit);
8521 },
8522
8523 "svg_circle" | "SVG圆形" | "SVGวงกลม" => {
8524 let cx = self.arg_num(&args, 0, 0.0)?;
8525 let cy = self.arg_num(&args, 1, 0.0)?;
8526 let r = self.arg_num(&args, 2, 5.0)?;
8527 let fill = self.arg_str(&args, 3, "#ffffff");
8528 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8529 svg.elements.push(format!(
8530 "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"
8531 ));
8532 }
8533 return Ok(Value::Unit);
8534 },
8535
8536 "svg_line" | "SVG线段" | "SVGเส้น" => {
8537 let x1 = self.arg_num(&args, 0, 0.0)?;
8538 let y1 = self.arg_num(&args, 1, 0.0)?;
8539 let x2 = self.arg_num(&args, 2, 0.0)?;
8540 let y2 = self.arg_num(&args, 3, 0.0)?;
8541 let stroke = self.arg_str(&args, 4, "#ffffff");
8542 let sw = self.arg_num(&args, 5, 1.0)?;
8543 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8544 svg.elements.push(format!(
8545 "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
8546 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8547 ));
8548 }
8549 return Ok(Value::Unit);
8550 },
8551
8552 "svg_polyline" | "SVG折线" | "SVGเส้นหัก" => {
8553 let pts = self.arg_str(&args, 0, "");
8554 let stroke = self.arg_str(&args, 1, "#ffffff");
8555 let sw = self.arg_num(&args, 2, 1.0)?;
8556 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8557 svg.elements.push(format!(
8558 "<polyline points=\"{pts}\" fill=\"none\" \
8559 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8560 ));
8561 }
8562 return Ok(Value::Unit);
8563 },
8564
8565 "svg_text" | "SVG文本" | "SVGข้อความ" => {
8566 let x = self.arg_num(&args, 0, 0.0)?;
8567 let y = self.arg_num(&args, 1, 0.0)?;
8568 let text = self.arg_str(&args, 2, "");
8569 let fill = self.arg_str(&args, 3, "#ffffff");
8570 let size = self.arg_num(&args, 4, 12.0)?;
8571 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8572 let safe = text
8573 .replace('&', "&")
8574 .replace('<', "<")
8575 .replace('>', ">");
8576 svg.elements.push(format!(
8577 "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
8578 font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"
8579 ));
8580 }
8581 return Ok(Value::Unit);
8582 },
8583
8584 "svg_end" | "结束SVG" | "จบSVG" => {
8585 {
8586 let borrow = self.svg.borrow();
8587 if let Some(svg) = borrow.as_ref() {
8588 svg.save()
8589 .map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
8590 }
8591 }
8592 *self.svg.borrow_mut() = None;
8593 return Ok(Value::Unit);
8594 },
8595
8596 "hsl_color" | "HSL颜色" | "สีHSL" => {
8597 let h = self.arg_num(&args, 0, 0.0)?;
8598 let s = self.arg_num(&args, 1, 70.0)?;
8599 let l = self.arg_num(&args, 2, 50.0)?;
8600 return Ok(Value::Str(hsl_to_hex(h, s, l)));
8601 },
8602
8603 #[cfg(not(target_arch = "wasm32"))]
8609 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" =>
8610 {
8611 if let Some(Value::List(v)) = args.first() {
8612 let samples: Vec<f32> = v
8613 .iter()
8614 .filter_map(|x| {
8615 if let Value::Number(n) = x {
8616 Some(*n as f32)
8617 } else {
8618 None
8619 }
8620 })
8621 .collect();
8622 self.fft.borrow_mut().push_samples(&samples);
8623 }
8624 return Ok(Value::Unit);
8625 },
8626
8627 #[cfg(not(target_arch = "wasm32"))]
8629 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" =>
8630 {
8631 let n = self.arg_num(&args, 0, 32.0)? as usize;
8632 let bands = self.fft.borrow().freq_bands(n);
8633 *self.fft_bands_cache.borrow_mut() = bands.clone();
8634 return Ok(Value::List(Rc::new(
8635 bands.into_iter().map(|v| Value::Number(v as f64)).collect(),
8636 )));
8637 },
8638
8639 #[cfg(not(target_arch = "wasm32"))]
8641 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" =>
8642 {
8643 return Ok(Value::Bool(self.fft.borrow().is_beat()));
8644 },
8645
8646 #[cfg(not(target_arch = "wasm32"))]
8648 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" =>
8649 {
8650 return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
8651 },
8652
8653 #[cfg(not(target_arch = "wasm32"))]
8655 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" => {
8656 return Ok(Value::Number(self.fft.borrow().rms() as f64));
8657 },
8658
8659 #[cfg(not(target_arch = "wasm32"))]
8661 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" =>
8662 {
8663 return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
8664 },
8665
8666 #[cfg(target_arch = "wasm32")]
8668 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" =>
8669 {
8670 return Ok(Value::Unit);
8671 },
8672 #[cfg(target_arch = "wasm32")]
8673 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" =>
8674 {
8675 let n = self.arg_num(&args, 0, 32.0)? as usize;
8676 return Ok(Value::List(vec![Value::Number(0.0); n].into()));
8677 },
8678 #[cfg(target_arch = "wasm32")]
8679 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" =>
8680 {
8681 return Ok(Value::Bool(false));
8682 },
8683 #[cfg(target_arch = "wasm32")]
8684 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" =>
8685 {
8686 return Ok(Value::Number(1.0));
8687 },
8688 #[cfg(target_arch = "wasm32")]
8689 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" => {
8690 return Ok(Value::Number(0.0));
8691 },
8692 #[cfg(target_arch = "wasm32")]
8693 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" =>
8694 {
8695 return Ok(Value::Number(0.0));
8696 },
8697
8698 "tex_checkerboard" | "ลายตารางหมากรุก" => {
8706 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8707 let tiles = self.arg_num(&args, 4, 8.0)? as u32;
8708 let (r1, g1, b1) = (
8709 self.arg_num(&args, 5, 255.)? as u32,
8710 self.arg_num(&args, 6, 255.)? as u32,
8711 self.arg_num(&args, 7, 255.)? as u32,
8712 );
8713 let (r2, g2, b2) = (
8714 self.arg_num(&args, 8, 0.)? as u32,
8715 self.arg_num(&args, 9, 0.)? as u32,
8716 self.arg_num(&args, 10, 0.)? as u32,
8717 );
8718 let c1 = (r1 << 16) | (g1 << 8) | b1;
8719 let c2 = (r2 << 16) | (g2 << 8) | b2;
8720 let mut gfx = self.gfx.borrow_mut();
8721 let (bw, bh) = (gfx.width, gfx.height);
8722 for row in 0..th {
8723 for col in 0..tw {
8724 let cx = col as u32 * tiles / tw as u32;
8725 let cy = row as u32 * tiles / th as u32;
8726 let (dx, dy) = (tx + col, ty + row);
8727 if dx < bw && dy < bh {
8728 gfx.buffer[dy * bw + dx] = if (cx + cy) % 2 == 0 { c1 } else { c2 };
8729 }
8730 }
8731 }
8732 return Ok(Value::Unit);
8733 },
8734
8735 "tex_gradient" | "ลายไล่สี" => {
8737 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8738 let angle = self.arg_num(&args, 4, 0.0)? as f32;
8739 let (r1, g1, b1) = (
8740 self.arg_num(&args, 5, 0.)? as f32 / 255.,
8741 self.arg_num(&args, 6, 0.)? as f32 / 255.,
8742 self.arg_num(&args, 7, 0.)? as f32 / 255.,
8743 );
8744 let (r2, g2, b2) = (
8745 self.arg_num(&args, 8, 255.)? as f32 / 255.,
8746 self.arg_num(&args, 9, 255.)? as f32 / 255.,
8747 self.arg_num(&args, 10, 255.)? as f32 / 255.,
8748 );
8749 let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
8750 let mut gfx = self.gfx.borrow_mut();
8751 let (bw, bh) = (gfx.width, gfx.height);
8752 for row in 0..th {
8753 for col in 0..tw {
8754 let nx = col as f32 / tw as f32 - 0.5;
8755 let ny = row as f32 / th as f32 - 0.5;
8756 let t = ((nx * ca + ny * sa + 0.707) / 1.414).clamp(0., 1.);
8757 let (dx, dy) = (tx + col, ty + row);
8758 if dx < bw && dy < bh {
8759 gfx.buffer[dy * bw + dx] =
8760 tex_rgb(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
8761 }
8762 }
8763 }
8764 return Ok(Value::Unit);
8765 },
8766
8767 "tex_noise" | "ลายนอยส์" => {
8769 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8770 let scale = self.arg_num(&args, 4, 4.0)? as f32;
8771 let octaves = self.arg_num(&args, 5, 4.0)? as u32;
8772 let seed = self.arg_num(&args, 6, 0.0)? as u32;
8773 let palette = self.arg_str(&args, 7, "rainbow");
8774 let mut gfx = self.gfx.borrow_mut();
8775 let (bw, bh) = (gfx.width, gfx.height);
8776 for row in 0..th {
8777 for col in 0..tw {
8778 let v = tex_fbm(
8779 col as f32 * scale / tw as f32,
8780 row as f32 * scale / th as f32,
8781 octaves,
8782 seed,
8783 );
8784 let [r, g, b] = tex_palette(&palette, v);
8785 let (dx, dy) = (tx + col, ty + row);
8786 if dx < bw && dy < bh {
8787 gfx.buffer[dy * bw + dx] = tex_rgb(r, g, b);
8788 }
8789 }
8790 }
8791 return Ok(Value::Unit);
8792 },
8793
8794 "tex_freq_map" | "ลายความถี่" => {
8797 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8798 let time = self.arg_num(&args, 4, 0.0)? as f32;
8799 let speed = self.arg_num(&args, 5, 0.3)? as f32;
8800 let palette = self.arg_str(&args, 6, "rainbow");
8801 let bands: Vec<f32> = {
8802 let c = self.fft_bands_cache.borrow();
8803 if c.is_empty() {
8804 vec![0.0; 32]
8805 } else {
8806 c.clone()
8807 }
8808 };
8809 let n = bands.len().max(1);
8810 let mut gfx = self.gfx.borrow_mut();
8811 let (bw, bh) = (gfx.width, gfx.height);
8812 for row in 0..th {
8813 for col in 0..tw {
8814 let band_idx = (col * n / tw.max(1)).min(n - 1);
8815 let mag = bands[band_idx].clamp(0., 1.);
8816 let fill_y = (mag * th as f32) as usize;
8817 if row >= th.saturating_sub(fill_y) {
8818 let t = (col as f32 / tw as f32 + time * speed) % 1.0;
8819 let [r, g, b] = tex_palette(&palette, t);
8820 let bright = mag * (1.0 - row as f32 / th as f32 * 0.5);
8821 let (dx, dy) = (tx + col, ty + row);
8822 if dx < bw && dy < bh {
8823 gfx.buffer[dy * bw + dx] =
8824 tex_rgb(r * bright, g * bright, b * bright);
8825 }
8826 }
8827 }
8828 }
8829 return Ok(Value::Unit);
8830 },
8831
8832 "tex_spiral" | "ลายเกลียวหมุน" => {
8834 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8835 let freq = self.arg_num(&args, 4, 5.0)? as f32;
8836 let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
8837 let time = self.arg_num(&args, 6, 0.0)? as f32;
8838 let palette = self.arg_str(&args, 7, "rainbow");
8839 let mut gfx = self.gfx.borrow_mut();
8840 let (bw, bh) = (gfx.width, gfx.height);
8841 for row in 0..th {
8842 for col in 0..tw {
8843 let nx = col as f32 / tw as f32 - 0.5;
8844 let ny = row as f32 / th as f32 - 0.5;
8845 let r = (nx * nx + ny * ny).sqrt();
8846 let theta = ny.atan2(nx);
8847 let t = ((r * freq - theta / std::f32::consts::TAU + time * 0.5) * n_bands
8848 % 1.0)
8849 .abs();
8850 let [cr, cg, cb] = tex_palette(&palette, t);
8851 let (dx, dy) = (tx + col, ty + row);
8852 if dx < bw && dy < bh {
8853 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8854 }
8855 }
8856 }
8857 return Ok(Value::Unit);
8858 },
8859
8860 "tex_ripple" | "ลายระลอก" => {
8862 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8863 let freq = self.arg_num(&args, 4, 10.0)? as f32;
8864 let rcx = self.arg_num(&args, 5, 0.5)? as f32;
8865 let rcy = self.arg_num(&args, 6, 0.5)? as f32;
8866 let time = self.arg_num(&args, 7, 0.0)? as f32;
8867 let palette = self.arg_str(&args, 8, "ocean");
8868 let mut gfx = self.gfx.borrow_mut();
8869 let (bw, bh) = (gfx.width, gfx.height);
8870 for row in 0..th {
8871 for col in 0..tw {
8872 let nx = col as f32 / tw as f32 - rcx;
8873 let ny = row as f32 / th as f32 - rcy;
8874 let r = (nx * nx + ny * ny).sqrt();
8875 let t = ((r * freq - time) % 1.0).abs();
8876 let [cr, cg, cb] = tex_palette(&palette, t);
8877 let (dx, dy) = (tx + col, ty + row);
8878 if dx < bw && dy < bh {
8879 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8880 }
8881 }
8882 }
8883 return Ok(Value::Unit);
8884 },
8885
8886 "tex_mandelbrot" | "ลายแมนเดลบรอต" => {
8888 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8889 let zoom = self.arg_num(&args, 4, 1.0)?;
8890 let mcx = self.arg_num(&args, 5, -0.5)?;
8891 let mcy = self.arg_num(&args, 6, 0.0)?;
8892 let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
8893 let palette = self.arg_str(&args, 8, "psychedelic");
8894 let mut gfx = self.gfx.borrow_mut();
8895 let (bw, bh) = (gfx.width, gfx.height);
8896 for row in 0..th {
8897 for col in 0..tw {
8898 let zx0 = (col as f64 / tw as f64 - 0.5) / zoom + mcx;
8899 let zy0 = (row as f64 / th as f64 - 0.5) / zoom + mcy;
8900 let mut x = 0.0f64;
8901 let mut y = 0.0f64;
8902 let mut i = 0u32;
8903 while i < max_iter && x * x + y * y < 4.0 {
8904 let t = x * x - y * y + zx0;
8905 y = 2.0 * x * y + zy0;
8906 x = t;
8907 i += 1;
8908 }
8909 let t = if i == max_iter {
8910 0.0f32
8911 } else {
8912 (i as f32
8913 - (x as f32 * x as f32 + y as f32 * y as f32).ln().ln()
8914 / 2.0f32.ln())
8915 / max_iter as f32
8916 };
8917 let [cr, cg, cb] = tex_palette(&palette, t.clamp(0., 1.));
8918 let (dx, dy) = (tx + col, ty + row);
8919 if dx < bw && dy < bh {
8920 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8921 }
8922 }
8923 }
8924 return Ok(Value::Unit);
8925 },
8926
8927 "tex_julia" | "ลายจูเลีย" => {
8929 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8930 let c_re = self.arg_num(&args, 4, -0.7)?;
8931 let c_im = self.arg_num(&args, 5, 0.27)?;
8932 let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
8933 let palette = self.arg_str(&args, 7, "neon");
8934 let mut gfx = self.gfx.borrow_mut();
8935 let (bw, bh) = (gfx.width, gfx.height);
8936 for row in 0..th {
8937 for col in 0..tw {
8938 let mut zx = (col as f64 / tw as f64 - 0.5) * 3.5;
8939 let mut zy = (row as f64 / th as f64 - 0.5) * 3.5;
8940 let mut i = 0u32;
8941 while i < max_iter && zx * zx + zy * zy < 4.0 {
8942 let t = zx * zx - zy * zy + c_re;
8943 zy = 2.0 * zx * zy + c_im;
8944 zx = t;
8945 i += 1;
8946 }
8947 let t = i as f32 / max_iter as f32;
8948 let [cr, cg, cb] = tex_palette(&palette, t);
8949 let (dx, dy) = (tx + col, ty + row);
8950 if dx < bw && dy < bh {
8951 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8952 }
8953 }
8954 }
8955 return Ok(Value::Unit);
8956 },
8957
8958 "tex_voronoi" | "ลายโวโรนอย" => {
8960 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8961 let cells = self.arg_num(&args, 4, 16.0)? as u32;
8962 let seed = self.arg_num(&args, 5, 42.0)? as u32;
8963 let palette = self.arg_str(&args, 6, "rainbow");
8964 let pts: Vec<[f32; 2]> = (0..cells)
8965 .map(|i| {
8966 [
8967 tex_hash(i as i32, 0, seed),
8968 tex_hash(i as i32, 1, seed + 999),
8969 ]
8970 })
8971 .collect();
8972 let mut gfx = self.gfx.borrow_mut();
8973 let (bw, bh) = (gfx.width, gfx.height);
8974 for row in 0..th {
8975 for col in 0..tw {
8976 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
8977 let (min_d, nearest) = pts.iter().enumerate().fold(
8978 (f32::MAX, 0usize),
8979 |(d, idx), (i, &[cx, cy])| {
8980 let dd = (fx - cx).powi(2) + (fy - cy).powi(2);
8981 if dd < d {
8982 (dd, i)
8983 } else {
8984 (d, idx)
8985 }
8986 },
8987 );
8988 let t = (nearest as f32 / cells as f32 + min_d * 4.0) % 1.0;
8989 let [cr, cg, cb] = tex_palette(&palette, t);
8990 let (dx, dy) = (tx + col, ty + row);
8991 if dx < bw && dy < bh {
8992 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8993 }
8994 }
8995 }
8996 return Ok(Value::Unit);
8997 },
8998
8999 "tex_halftone" | "ลายฮาล์ฟโทน" => {
9001 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9002 let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
9003 let time = self.arg_num(&args, 5, 0.0)? as f32;
9004 let palette = self.arg_str(&args, 6, "rainbow");
9005 let mut gfx = self.gfx.borrow_mut();
9006 let (bw, bh) = (gfx.width, gfx.height);
9007 for row in 0..th {
9008 for col in 0..tw {
9009 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
9010 let gx = (fx / dot_size).floor();
9011 let gy = (fy / dot_size).floor();
9012 let lx = (fx / dot_size - gx - 0.5) * 2.0;
9013 let ly = (fy / dot_size - gy - 0.5) * 2.0;
9014 let r = (lx * lx + ly * ly).sqrt();
9015 let t = (gx / (1.0 / dot_size) + time * 0.1) % 1.0;
9016 let a = if r < 0.7 {
9017 ((0.7 - r) / 0.7).clamp(0., 1.)
9018 } else {
9019 0.0
9020 };
9021 if a > 0.0 {
9022 let [cr, cg, cb] = tex_palette(&palette, t);
9023 let (dx, dy) = (tx + col, ty + row);
9024 if dx < bw && dy < bh {
9025 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9026 }
9027 }
9028 }
9029 }
9030 return Ok(Value::Unit);
9031 },
9032
9033 "set_shade_mode" | "设置着色" | "シェード設定" | "셰이드모드" | "ตั้งการแรเงา" =>
9038 {
9039 let m = self.arg_num(&args, 0, 2.0)? as u8;
9040 self.gfx.borrow_mut().shade_mode = m;
9041 return Ok(Value::Unit);
9042 },
9043 "set_cel_bands" | "设置色阶" | "セル段数" | "셀밴드" | "ตั้งระดับสี" =>
9045 {
9046 let n = (self.arg_num(&args, 0, 4.0)? as u32).max(2);
9047 self.gfx.borrow_mut().shade.bands = n;
9048 return Ok(Value::Unit);
9049 },
9050 "set_shadow_color" | "设置阴影色" | "影の色" | "그림자색" | "ตั้งสีเงา" =>
9052 {
9053 let r = self.arg_num(&args, 0, 26.)? as f32 / 255.0;
9054 let g = self.arg_num(&args, 1, 33.)? as f32 / 255.0;
9055 let b = self.arg_num(&args, 2, 77.)? as f32 / 255.0;
9056 self.gfx.borrow_mut().shade.shadow = [r, g, b];
9057 return Ok(Value::Unit);
9058 },
9059 #[cfg(not(target_arch = "wasm32"))]
9065 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" =>
9066 {
9067 let s = self.arg_str(&args, 0, "");
9068 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9069 s.as_bytes(),
9070 ))));
9071 },
9072 #[cfg(target_arch = "wasm32")]
9073 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" =>
9074 {
9075 let s = self.arg_str(&args, 0, "");
9076 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9077 s.as_bytes(),
9078 ))));
9079 },
9080 #[cfg(not(target_arch = "wasm32"))]
9082 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" => {
9083 let s = self.arg_str(&args, 0, "");
9084 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9085 let mut out = Vec::with_capacity(shape.points.len() * 3);
9086 for p in &shape.points {
9087 out.push(Value::Number(p[0] as f64));
9088 out.push(Value::Number(p[1] as f64));
9089 out.push(Value::Number(p[2] as f64));
9090 }
9091 return Ok(Value::List(Rc::new(out)));
9092 },
9093 #[cfg(target_arch = "wasm32")]
9094 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" => {
9095 let s = self.arg_str(&args, 0, "");
9096 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9097 let mut out = Vec::with_capacity(shape.points.len() * 3);
9098 for p in &shape.points {
9099 out.push(Value::Number(p[0] as f64));
9100 out.push(Value::Number(p[1] as f64));
9101 out.push(Value::Number(p[2] as f64));
9102 }
9103 return Ok(Value::List(out.into()));
9104 },
9105 #[cfg(not(target_arch = "wasm32"))]
9106 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" =>
9107 {
9108 let s = self.arg_str(&args, 0, "");
9109 return Ok(Value::Str(
9110 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9111 ));
9112 },
9113 #[cfg(target_arch = "wasm32")]
9114 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" =>
9115 {
9116 let s = self.arg_str(&args, 0, "");
9117 return Ok(Value::Str(
9118 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9119 ));
9120 },
9121 #[cfg(not(target_arch = "wasm32"))]
9123 "knot_keygen" | "hybrid_keygen" | "สร้างกุญแจปม" | "生成密钥" | "鍵生成" | "키생성" =>
9124 {
9125 self.crypto_ids.push(ling_crypto::KnotIdentity::generate());
9126 return Ok(Value::Number((self.crypto_ids.len() - 1) as f64));
9127 },
9128 #[cfg(not(target_arch = "wasm32"))]
9129 "knot_public" | "hybrid_public" | "กุญแจสาธารณะปม" | "公钥" | "公開鍵" | "공개키" =>
9130 {
9131 let h = self.arg_num(&args, 0, 0.0)? as usize;
9132 let pk = self
9133 .crypto_ids
9134 .get(h)
9135 .map(|id| hex_encode(id.public_key()))
9136 .unwrap_or_default();
9137 return Ok(Value::Str(pk));
9138 },
9139 #[cfg(not(target_arch = "wasm32"))]
9141 "knot_encapsulate"
9142 | "hybrid_encapsulate"
9143 | "ห่อกุญแจปม"
9144 | "封装密钥"
9145 | "カプセル化"
9146 | "캡슐화" => {
9147 let pk = hex_decode(&self.arg_str(&args, 0, ""));
9148 match ling_crypto::geo::knot_encapsulate(&pk) {
9149 Ok((ct, ss)) => {
9150 return Ok(Value::List(Rc::new(vec![
9151 Value::Str(hex_encode(&ct)),
9152 Value::Str(hex_encode(&ss)),
9153 ])))
9154 },
9155 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9156 }
9157 },
9158 #[cfg(not(target_arch = "wasm32"))]
9160 "knot_decapsulate"
9161 | "hybrid_decapsulate"
9162 | "แกะกุญแจปม"
9163 | "解封装密钥"
9164 | "カプセル解除"
9165 | "캡슐해제" => {
9166 let h = self.arg_num(&args, 0, 0.0)? as usize;
9167 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9168 let ss = self
9169 .crypto_ids
9170 .get(h)
9171 .and_then(|id| id.decapsulate(&ct).ok())
9172 .map(|s| hex_encode(&s))
9173 .unwrap_or_default();
9174 return Ok(Value::Str(ss));
9175 },
9176 #[cfg(not(target_arch = "wasm32"))]
9178 "crypto_seal" | "ผนึก" | "封印" | "封印する" | "봉인" => {
9179 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9180 let pt = self.arg_str(&args, 1, "");
9181 match ling_crypto::geo::holo_seal(key, pt.as_bytes()) {
9182 Ok(ct) => return Ok(Value::Str(hex_encode(&ct))),
9183 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9184 }
9185 },
9186 #[cfg(not(target_arch = "wasm32"))]
9187 "crypto_open" | "เปิดผนึก" | "解封" | "封印解除" | "봉인해제" =>
9188 {
9189 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9190 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9191 match ling_crypto::geo::holo_open(key, &ct) {
9192 Ok(pt) => return Ok(Value::Str(String::from_utf8_lossy(&pt).into_owned())),
9193 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9194 }
9195 },
9196 #[cfg(not(target_arch = "wasm32"))]
9198 "holo_points" | "จุดโฮโลแกรม" | "全息点" | "ホログラム点" | "홀로그램점" =>
9199 {
9200 let s = self.arg_str(&args, 0, "");
9201 let frags = ling_crypto::geo::scatter(s.as_bytes());
9202 let mut out = Vec::with_capacity(frags.len() * 4);
9203 for f in &frags {
9204 for c in f.coord {
9205 out.push(Value::Number(c as f64));
9206 }
9207 }
9208 return Ok(Value::List(Rc::new(out)));
9209 },
9210 #[cfg(not(target_arch = "wasm32"))]
9211 "holo_fragment_count"
9212 | "จำนวนชิ้นโฮโลแกรม"
9213 | "全息碎片数"
9214 | "ホログラム断片数"
9215 | "홀로그램조각수" => {
9216 let s = self.arg_str(&args, 0, "");
9217 return Ok(Value::Number(
9218 ling_crypto::geo::scatter(s.as_bytes()).len() as f64
9219 ));
9220 },
9221 #[cfg(not(target_arch = "wasm32"))]
9224 "shake_hex" | "SHAKE哈希" => {
9225 let s = self.arg_str(&args, 0, "");
9226 let len = self.arg_num(&args, 1, 32.0)?.max(0.0) as usize;
9227 return Ok(Value::Str(hex_encode(&ling_crypto::Shake256::hash(s.as_bytes(), len))));
9228 },
9229 #[cfg(not(target_arch = "wasm32"))]
9231 "ed25519_keygen" | "생성서명키" => {
9232 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::generate());
9233 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9234 },
9235 #[cfg(not(target_arch = "wasm32"))]
9240 "ed25519_keygen_from_seed" | "씨앗에서생성서명키" => {
9241 let seed = hex_to_32(&self.arg_str(&args, 0, ""));
9242 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::from_seed(seed));
9243 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9244 },
9245 #[cfg(not(target_arch = "wasm32"))]
9246 "ed25519_public" | "서명공개키" => {
9247 let h = self.arg_num(&args, 0, 0.0)? as usize;
9248 let pk = self
9249 .ed25519_ids
9250 .get(h)
9251 .map(|kp| hex_encode(&kp.public_key()))
9252 .unwrap_or_default();
9253 return Ok(Value::Str(pk));
9254 },
9255 #[cfg(not(target_arch = "wasm32"))]
9257 "ed25519_sign" | "서명하다" => {
9258 let h = self.arg_num(&args, 0, 0.0)? as usize;
9259 let msg = self.arg_str(&args, 1, "");
9260 let sig = self
9261 .ed25519_ids
9262 .get(h)
9263 .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
9264 .unwrap_or_default();
9265 return Ok(Value::Str(sig));
9266 },
9267 #[cfg(not(target_arch = "wasm32"))]
9269 "ed25519_verify" | "서명확인" => {
9270 let pk_hex = self.arg_str(&args, 0, "");
9271 let msg = self.arg_str(&args, 1, "");
9272 let sig_hex = self.arg_str(&args, 2, "");
9273 let pk_bytes = hex_decode(&pk_hex);
9274 let sig_bytes = hex_decode(&sig_hex);
9275 let ok = (|| {
9276 let pk: [u8; 32] = pk_bytes.try_into().ok()?;
9277 let sig: [u8; 64] = sig_bytes.try_into().ok()?;
9278 Some(ling_crypto::Ed25519Keypair::verify(&pk, msg.as_bytes(), &sig).is_ok())
9279 })()
9280 .unwrap_or(false);
9281 return Ok(Value::Bool(ok));
9282 },
9283 #[cfg(not(target_arch = "wasm32"))]
9286 "password_hash" | "비밀번호해시" => {
9287 let pw = self.arg_str(&args, 0, "");
9288 let hash = ling_crypto::Argon2idParams::default()
9289 .hash_password(pw.as_bytes())
9290 .unwrap_or_default();
9291 return Ok(Value::Str(hash));
9292 },
9293 #[cfg(not(target_arch = "wasm32"))]
9294 "password_verify" | "비밀번호확인" => {
9295 let pw = self.arg_str(&args, 0, "");
9296 let hash = self.arg_str(&args, 1, "");
9297 let ok = ling_crypto::Argon2idParams::verify_password(pw.as_bytes(), &hash).is_ok();
9298 return Ok(Value::Bool(ok));
9299 },
9300 #[cfg(not(target_arch = "wasm32"))]
9303 "random_hex" | "무작위16진수" => {
9304 use rand::RngCore;
9305 let n = self.arg_num(&args, 0, 16.0)?.max(0.0) as usize;
9306 let mut buf = vec![0u8; n];
9307 rand::rngs::OsRng.fill_bytes(&mut buf);
9308 return Ok(Value::Str(hex_encode(&buf)));
9309 },
9310 #[cfg(not(target_arch = "wasm32"))]
9314 "base64_encode" | "base64인코딩" => {
9315 use base64::Engine as _;
9316 let s = self.arg_str(&args, 0, "");
9317 return Ok(Value::Str(
9318 base64::engine::general_purpose::STANDARD.encode(s.as_bytes()),
9319 ));
9320 },
9321 #[cfg(not(target_arch = "wasm32"))]
9325 "base64_decode_to_file" | "base64파일로저장" => {
9326 use base64::Engine as _;
9327 let b64 = self.arg_str(&args, 0, "");
9328 let path = self.arg_str(&args, 1, "");
9329 let b64 = b64
9330 .split(',')
9331 .next_back()
9332 .unwrap_or(&b64); let ok = base64::engine::general_purpose::STANDARD
9334 .decode(b64)
9335 .ok()
9336 .and_then(|bytes| std::fs::write(&path, bytes).ok())
9337 .is_some();
9338 return Ok(Value::Bool(ok));
9339 },
9340 #[cfg(not(target_arch = "wasm32"))]
9345 "qr_svg" | "QR코드" => {
9346 let text = self.arg_str(&args, 0, "");
9347 let svg = qrcode::QrCode::new(text.as_bytes())
9348 .map(|code| {
9349 code.render::<qrcode::render::svg::Color>()
9350 .min_dimensions(240, 240)
9351 .dark_color(qrcode::render::svg::Color("#1a0f3d"))
9352 .light_color(qrcode::render::svg::Color("#ffffff"))
9353 .build()
9354 })
9355 .unwrap_or_default();
9356 return Ok(Value::Str(svg));
9357 },
9358 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9361 "zip_files" | "압축파일" => {
9362 let paths = match args.first() {
9363 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9364 _ => Vec::new(),
9365 };
9366 let out_path = self.arg_str(&args, 1, "out.zip");
9367 let ok = (|| -> std::io::Result<()> {
9368 let file = std::fs::File::create(&out_path)?;
9369 let mut writer = zip::ZipWriter::new(file);
9370 let options: zip::write::FileOptions<'_, ()> = zip::write::FileOptions::default()
9371 .compression_method(zip::CompressionMethod::Deflated);
9372 for p in &paths {
9373 let name = std::path::Path::new(p)
9374 .file_name()
9375 .map(|n| n.to_string_lossy().into_owned())
9376 .unwrap_or_else(|| p.clone());
9377 let bytes = std::fs::read(p)?;
9378 writer.start_file(name, options)?;
9379 std::io::Write::write_all(&mut writer, &bytes)?;
9380 }
9381 writer.finish()?;
9382 Ok(())
9383 })()
9384 .is_ok();
9385 return Ok(Value::Bool(ok));
9386 },
9387 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9394 "pdf_from_images" | "PDF来自图片" => {
9395 let paths = match args.first() {
9396 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9397 _ => Vec::new(),
9398 };
9399 let out_path = self.arg_str(&args, 1, "out.pdf");
9400 let ok = build_pdf_from_images(&paths, &out_path).is_ok();
9401 return Ok(Value::Bool(ok));
9402 },
9403
9404 "ease" => {
9408 let name = self.arg_str(&args, 0, "ease");
9409 let t = self.arg_num(&args, 1, 0.0)? as f32;
9410 return Ok(Value::Number(
9411 ling_ui::Easing::from_name(&name).apply(t) as f64
9412 ));
9413 },
9414
9415 "tween" | "补间" | "補間" | "트윈" | "แทรกค่า" => {
9420 let a = self.arg_num(&args, 0, 0.0)?;
9421 let b = self.arg_num(&args, 1, 0.0)?;
9422 let t = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 1.0);
9423 return Ok(Value::Number(a + (b - a) * t));
9424 },
9425 "tween_ease" | "缓动补间" | "緩和補間" | "이징트윈" | "แทรกนุ่ม" =>
9426 {
9427 let a = self.arg_num(&args, 0, 0.0)? as f32;
9428 let b = self.arg_num(&args, 1, 0.0)? as f32;
9429 let t = self.arg_num(&args, 2, 0.0)? as f32;
9430 let kind = self.arg_str(&args, 3, "linear");
9431 let e = ling_animation::EaseFunction::from_name(&kind);
9432 return Ok(Value::Number(
9433 ling_animation::ease::tween_ease(&a, &b, t, e) as f64,
9434 ));
9435 },
9436 "breathe" | "呼吸" | "호흡" | "หายใจ" => {
9438 let t = self.arg_num(&args, 0, 0.0)? as f32;
9439 let rate = self.arg_num(&args, 1, 1.0)? as f32;
9440 let depth = self.arg_num(&args, 2, 0.1)? as f32;
9441 return Ok(Value::Number(
9442 ling_animation::scalar::breathe(t, rate, depth) as f64,
9443 ));
9444 },
9445 "wobble" | "摆动" | "揺れ" | "흔들림" | "โยก" => {
9446 let t = self.arg_num(&args, 0, 0.0)? as f32;
9447 let freq = self.arg_num(&args, 1, 1.0)? as f32;
9448 let amp = self.arg_num(&args, 2, 1.0)? as f32;
9449 let phase = self.arg_num(&args, 3, 0.0)? as f32;
9450 return Ok(Value::Number(
9451 ling_animation::scalar::wobble(t, freq, amp, phase) as f64,
9452 ));
9453 },
9454 "gait_phase" | "步相" | "歩相" | "걸음위상" | "เฟสก้าว" => {
9455 let t = self.arg_num(&args, 0, 0.0)? as f32;
9456 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9457 return Ok(Value::Number(
9458 ling_animation::scalar::gait_phase(t, speed) as f64
9459 ));
9460 },
9461 "gait_swing" | "步摆" | "歩振り" | "걸음흔들" | "ก้าวแกว่ง" =>
9462 {
9463 let t = self.arg_num(&args, 0, 0.0)? as f32;
9464 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9465 let stride = self.arg_num(&args, 2, 1.0)? as f32;
9466 return Ok(Value::Number(
9467 ling_animation::scalar::gait_swing(t, speed, stride) as f64,
9468 ));
9469 },
9470 "gait_lift" | "抬脚" | "足上げ" | "발들기" | "ยกเท้า" => {
9471 let t = self.arg_num(&args, 0, 0.0)? as f32;
9472 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9473 let height = self.arg_num(&args, 2, 1.0)? as f32;
9474 return Ok(Value::Number(
9475 ling_animation::scalar::gait_lift(t, speed, height) as f64,
9476 ));
9477 },
9478 "spring_to" | "弹向" | "バネ寄せ" | "스프링이동" | "สปริงไป" =>
9479 {
9480 let pos = self.arg_num(&args, 0, 0.0)? as f32;
9481 let vel = self.arg_num(&args, 1, 0.0)? as f32;
9482 let target = self.arg_num(&args, 2, 0.0)? as f32;
9483 let stiffness = self.arg_num(&args, 3, 120.0)? as f32;
9484 let damping = self.arg_num(&args, 4, 14.0)? as f32;
9485 let dt = self.arg_num(&args, 5, 1.0 / 60.0)? as f32;
9486 let (np, nv) =
9487 ling_animation::scalar::spring_step(pos, vel, target, stiffness, damping, dt);
9488 return Ok(Value::List(Rc::new(vec![
9489 Value::Number(np as f64),
9490 Value::Number(nv as f64),
9491 ])));
9492 },
9493 "ik2" | "反解" | "逆運動" | "역운동" | "ไอเค2" => {
9494 let l1 = self.arg_num(&args, 0, 1.0)? as f32;
9495 let l2 = self.arg_num(&args, 1, 1.0)? as f32;
9496 let tx = self.arg_num(&args, 2, 0.0)? as f32;
9497 let ty = self.arg_num(&args, 3, 0.0)? as f32;
9498 let (sh, el) = ling_animation::scalar::two_bone_ik(l1, l2, tx, ty);
9499 return Ok(Value::List(Rc::new(vec![
9500 Value::Number(sh as f64),
9501 Value::Number(el as f64),
9502 ])));
9503 },
9504 "gear_couple" | "齿轮联动" | "歯車連動" | "기어연동" | "เฟืองทด" =>
9506 {
9507 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9508 let ti = self.arg_num(&args, 1, 1.0)? as f32;
9509 let to = self.arg_num(&args, 2, 1.0)? as f32;
9510 return Ok(Value::Number(
9511 ling_animation::scalar::gear(angle, ti, to) as f64
9512 ));
9513 },
9514 "gear_train" | "齿轮组" | "歯車列" | "기어열" | "ชุดเฟือง" => {
9515 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9516 let teeth: Vec<f32> = match args.get(1) {
9517 Some(Value::List(items)) => items
9518 .iter()
9519 .filter_map(|v| {
9520 if let Value::Number(n) = v {
9521 Some(*n as f32)
9522 } else {
9523 None
9524 }
9525 })
9526 .collect(),
9527 _ => Vec::new(),
9528 };
9529 let out = ling_animation::mechanism::gear_train(angle, &teeth);
9530 return Ok(Value::List(Rc::new(
9531 out.into_iter().map(|a| Value::Number(a as f64)).collect(),
9532 )));
9533 },
9534 "cam_lift" | "凸轮升程" | "カム揚程" | "캠리프트" | "ยกลูกเบี้ยว" =>
9535 {
9536 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9537 let lift = self.arg_num(&args, 1, 1.0)? as f32;
9538 return Ok(Value::Number(
9539 ling_animation::scalar::cam_lift(angle, lift) as f64
9540 ));
9541 },
9542 "piston" | "活塞" | "ピストン" | "피스톤" | "ลูกสูบ" => {
9543 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9544 let crank = self.arg_num(&args, 1, 1.0)? as f32;
9545 let rod = self.arg_num(&args, 2, 2.0)? as f32;
9546 return Ok(Value::Number(
9547 ling_animation::scalar::piston(angle, crank, rod) as f64,
9548 ));
9549 },
9550 "rack" | "齿条" | "ラック" | "랙" | "แร็ค" => {
9551 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9552 let radius = self.arg_num(&args, 1, 1.0)? as f32;
9553 return Ok(Value::Number(
9554 ling_animation::scalar::rack(angle, radius) as f64
9555 ));
9556 },
9557 #[cfg(not(target_arch = "wasm32"))]
9558 "mouse_x" => {
9559 let gfx = self.gfx.borrow();
9560 let v = gfx
9561 .window
9562 .as_ref()
9563 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9564 .map(|p| p.0 as f64)
9565 .unwrap_or(0.0);
9566 return Ok(Value::Number(v));
9567 },
9568 #[cfg(target_arch = "wasm32")]
9569 "mouse_x" => {
9570 return Ok(Value::Number(crate::gfx::wasm_mouse_x() as f64));
9571 },
9572 #[cfg(not(target_arch = "wasm32"))]
9573 "mouse_y" => {
9574 let gfx = self.gfx.borrow();
9575 let v = gfx
9576 .window
9577 .as_ref()
9578 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9579 .map(|p| p.1 as f64)
9580 .unwrap_or(0.0);
9581 return Ok(Value::Number(v));
9582 },
9583 #[cfg(target_arch = "wasm32")]
9584 "mouse_y" => {
9585 return Ok(Value::Number(crate::gfx::wasm_mouse_y() as f64));
9586 },
9587 #[cfg(not(target_arch = "wasm32"))]
9588 "mouse_down" => {
9589 let mut gfx = self.gfx.borrow_mut();
9590 let d = !gfx.input_suppressed()
9591 && gfx
9592 .window
9593 .as_ref()
9594 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
9595 .unwrap_or(false);
9596 return Ok(Value::Bool(d));
9597 },
9598 #[cfg(target_arch = "wasm32")]
9599 "mouse_down" => {
9600 return Ok(Value::Bool(crate::gfx::wasm_mouse_down()));
9601 },
9602 #[cfg(not(target_arch = "wasm32"))]
9603 "mouse_down_right" | "เมาส์ขวา" => {
9604 let mut gfx = self.gfx.borrow_mut();
9605 let d = !gfx.input_suppressed()
9606 && gfx
9607 .window
9608 .as_ref()
9609 .map(|w| w.get_mouse_down(minifb::MouseButton::Right))
9610 .unwrap_or(false);
9611 return Ok(Value::Bool(d));
9612 },
9613 #[cfg(target_arch = "wasm32")]
9614 "mouse_down_right" | "เมาส์ขวา" => {
9615 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_right()));
9616 },
9617 #[cfg(not(target_arch = "wasm32"))]
9618 "mouse_down_middle" | "เมาส์กลาง" => {
9619 let mut gfx = self.gfx.borrow_mut();
9620 let d = !gfx.input_suppressed()
9621 && gfx
9622 .window
9623 .as_ref()
9624 .map(|w| w.get_mouse_down(minifb::MouseButton::Middle))
9625 .unwrap_or(false);
9626 return Ok(Value::Bool(d));
9627 },
9628 #[cfg(target_arch = "wasm32")]
9629 "mouse_down_middle" | "เมาส์กลาง" => {
9630 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_middle()));
9631 },
9632 #[cfg(not(target_arch = "wasm32"))]
9633 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" =>
9634 {
9635 let x = self.arg_num(&args, 0, 0.0)? as f32;
9636 let y = self.arg_num(&args, 1, 0.0)? as f32;
9637 let w = self.arg_num(&args, 2, 0.0)? as f32;
9638 let h = self.arg_num(&args, 3, 0.0)? as f32;
9639 let gfx = self.gfx.borrow();
9640 let (mx, my) = gfx
9641 .window
9642 .as_ref()
9643 .and_then(|win| win.get_mouse_pos(minifb::MouseMode::Clamp))
9644 .unwrap_or((0.0, 0.0));
9645 return Ok(Value::Bool(ling_ui::holo::hit_rect(mx, my, x, y, w, h)));
9646 },
9647 #[cfg(target_arch = "wasm32")]
9648 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" =>
9649 {
9650 return Ok(Value::Bool(false));
9651 },
9652 "ui_text" | "界面文字" | "UI文字" | "UI텍스트" | "ข้อความหน้าจอ" =>
9654 {
9655 let x = self.arg_num(&args, 0, 0.0)? as f32;
9656 let y = self.arg_num(&args, 1, 0.0)? as f32;
9657 let scale = self.arg_num(&args, 2, 16.0)? as f32;
9658 let s = self.arg_str(&args, 3, "");
9659 let segs = ling_ui::holo::text_lines(&s, x, y, scale * 0.62, scale, scale * 0.24);
9660 let mut gfx = self.gfx.borrow_mut();
9661 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
9662 for sg in segs {
9663 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
9664 }
9665 return Ok(Value::Unit);
9666 },
9667 #[cfg(not(target_arch = "wasm32"))]
9670 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" =>
9671 {
9672 let path = self.arg_str(&args, 0, "");
9673 let weight = match self.arg_num(&args, 1, 0.0)? {
9675 w if w > 0.0 => Some(w as f32),
9676 _ => None,
9677 };
9678 let mut loaded = ling_graphics::VectorFont::from_path_weight(&path, weight);
9680 if loaded.is_err() {
9681 if let Some(dir) = &self.source_dir {
9682 let joined = dir.join(&path);
9683 loaded = ling_graphics::VectorFont::from_path_weight(
9684 &joined.to_string_lossy(),
9685 weight,
9686 );
9687 }
9688 }
9689 match loaded {
9690 Ok(f) => {
9691 let id = self.fonts.len();
9692 self.fonts.push(f);
9693 return Ok(Value::Number(id as f64));
9694 },
9695 Err(e) => {
9696 eprintln!("font_load failed ({path}): {e}");
9697 return Ok(Value::Number(-1.0));
9698 },
9699 }
9700 },
9701 #[cfg(target_arch = "wasm32")]
9702 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" =>
9703 {
9704 return Ok(Value::Number(-1.0));
9707 },
9708 #[cfg(not(target_arch = "wasm32"))]
9713 "image_load" =>
9714 {
9715 let path = self.arg_str(&args, 0, "");
9716 let mut loaded = image::open(&path);
9717 if loaded.is_err() {
9718 if let Some(dir) = &self.source_dir {
9719 let joined = dir.join(&path);
9720 loaded = image::open(&joined);
9721 }
9722 }
9723 match loaded {
9724 Ok(img) => {
9725 let id = self.images.len();
9726 self.images.push(img.to_rgba8());
9727 return Ok(Value::Number(id as f64));
9728 },
9729 Err(e) => {
9730 eprintln!("image_load failed ({path}): {e}");
9731 return Ok(Value::Number(-1.0));
9732 },
9733 }
9734 },
9735 #[cfg(target_arch = "wasm32")]
9736 "image_load" =>
9737 {
9738 return Ok(Value::Number(-1.0));
9740 },
9741 "image_width" =>
9742 {
9743 let id = self.arg_num(&args, 0, -1.0)? as i64;
9744 if id >= 0 && (id as usize) < self.images.len() {
9745 return Ok(Value::Number(self.images[id as usize].width() as f64));
9746 }
9747 return Ok(Value::Number(0.0));
9748 },
9749 "image_height" =>
9750 {
9751 let id = self.arg_num(&args, 0, -1.0)? as i64;
9752 if id >= 0 && (id as usize) < self.images.len() {
9753 return Ok(Value::Number(self.images[id as usize].height() as f64));
9754 }
9755 return Ok(Value::Number(0.0));
9756 },
9757 "image_pixel_r" | "image_pixel_g" | "image_pixel_b" | "image_pixel_a" =>
9758 {
9759 let id = self.arg_num(&args, 0, -1.0)? as i64;
9760 let px = self.arg_num(&args, 1, 0.0)? as i64;
9761 let py = self.arg_num(&args, 2, 0.0)? as i64;
9762 if id >= 0 && (id as usize) < self.images.len() {
9763 let img = &self.images[id as usize];
9764 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
9765 let p = img.get_pixel(px as u32, py as u32);
9766 let ch = match name {
9767 "image_pixel_r" => p[0],
9768 "image_pixel_g" => p[1],
9769 "image_pixel_b" => p[2],
9770 _ => p[3],
9771 };
9772 return Ok(Value::Number(ch as f64));
9773 }
9774 }
9775 return Ok(Value::Number(0.0));
9776 },
9777 "image_new" =>
9786 {
9787 let w = self.arg_num(&args, 0, 1.0)?.max(1.0) as u32;
9788 let h = self.arg_num(&args, 1, 1.0)?.max(1.0) as u32;
9789 let id = self.images.len();
9790 self.images.push(image::RgbaImage::new(w, h));
9791 return Ok(Value::Number(id as f64));
9792 },
9793 "image_set_pixel" =>
9798 {
9799 let id = self.arg_num(&args, 0, -1.0)? as i64;
9800 let px = self.arg_num(&args, 1, 0.0)? as i64;
9801 let py = self.arg_num(&args, 2, 0.0)? as i64;
9802 let r = self.arg_num(&args, 3, 0.0)?.clamp(0.0, 255.0) as u8;
9803 let g = self.arg_num(&args, 4, 0.0)?.clamp(0.0, 255.0) as u8;
9804 let b = self.arg_num(&args, 5, 0.0)?.clamp(0.0, 255.0) as u8;
9805 let a = self.arg_num(&args, 6, 255.0)?.clamp(0.0, 255.0) as u8;
9806 if id >= 0 && (id as usize) < self.images.len() {
9807 let img = &mut self.images[id as usize];
9808 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
9809 img.put_pixel(px as u32, py as u32, image::Rgba([r, g, b, a]));
9810 }
9811 }
9812 return Ok(Value::Unit);
9813 },
9814 #[cfg(not(target_arch = "wasm32"))]
9822 "image_save" =>
9823 {
9824 let id = self.arg_num(&args, 0, -1.0)? as i64;
9825 let path = self.arg_str(&args, 1, "");
9826 if id >= 0 && (id as usize) < self.images.len() {
9827 if let Some(parent) = std::path::Path::new(&path).parent() {
9828 if !parent.as_os_str().is_empty() {
9829 let _ = std::fs::create_dir_all(parent);
9830 }
9831 }
9832 if self.images[id as usize].save(&path).is_ok() {
9833 return Ok(Value::Number(1.0));
9834 }
9835 }
9836 return Ok(Value::Number(-1.0));
9837 },
9838 #[cfg(target_arch = "wasm32")]
9839 "image_save" =>
9840 {
9841 return Ok(Value::Number(-1.0));
9842 },
9843 #[cfg(not(target_arch = "wasm32"))]
9846 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" =>
9847 {
9848 let id = self.arg_num(&args, 0, 0.0)? as i64;
9849 let x = self.arg_num(&args, 1, 0.0)? as f32;
9850 let y = self.arg_num(&args, 2, 0.0)? as f32;
9851 let px = self.arg_num(&args, 3, 16.0)? as f32;
9852 let s = self.arg_str(&args, 4, "");
9853 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
9854 let strokes = self.font_layout_2d(id as usize, x, y, px, &s);
9855 let mut gfx = self.gfx.borrow_mut();
9856 let (w, h, color, add, aa) =
9857 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
9858 for pl in &strokes {
9859 for seg in pl.windows(2) {
9860 if aa {
9861 crate::gfx::raster::draw_line_aa(
9862 &mut gfx.buffer,
9863 w,
9864 h,
9865 color,
9866 add,
9867 seg[0][0],
9868 seg[0][1],
9869 seg[1][0],
9870 seg[1][1],
9871 );
9872 } else {
9873 crate::gfx::raster::draw_line(
9874 &mut gfx.buffer,
9875 w,
9876 h,
9877 color,
9878 seg[0][0],
9879 seg[0][1],
9880 seg[1][0],
9881 seg[1][1],
9882 );
9883 }
9884 }
9885 }
9886 }
9887 return Ok(Value::Unit);
9888 },
9889 #[cfg(target_arch = "wasm32")]
9890 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" =>
9891 {
9892 return Ok(Value::Unit);
9893 },
9894 #[cfg(not(target_arch = "wasm32"))]
9897 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" =>
9898 {
9899 let id = self.arg_num(&args, 0, 0.0)? as i64;
9900 let x = self.arg_num(&args, 1, 0.0)? as f32;
9901 let y = self.arg_num(&args, 2, 0.0)? as f32;
9902 let px = self.arg_num(&args, 3, 16.0)? as f32;
9903 let s = self.arg_str(&args, 4, "");
9904 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
9905 let glyphs = self.font_layout_2d_glyphs(id as usize, x, y, px, &s);
9907 let mut gfx = self.gfx.borrow_mut();
9908 let (w, h, color, add, aa) =
9909 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
9910 for contours in &glyphs {
9911 if aa {
9912 crate::gfx::raster::fill_contours_aa(
9913 &mut gfx.buffer,
9914 w,
9915 h,
9916 color,
9917 add,
9918 contours,
9919 );
9920 } else {
9921 crate::gfx::raster::fill_contours(
9922 &mut gfx.buffer,
9923 w,
9924 h,
9925 color,
9926 add,
9927 contours,
9928 );
9929 }
9930 }
9931 }
9932 return Ok(Value::Unit);
9933 },
9934 #[cfg(target_arch = "wasm32")]
9935 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" =>
9936 {
9937 return Ok(Value::Unit);
9938 },
9939 #[cfg(not(target_arch = "wasm32"))]
9943 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" =>
9944 {
9945 let id = self.arg_num(&args, 0, 0.0)? as i64;
9946 let cx = self.arg_num(&args, 1, 0.0)? as f32;
9947 let cy = self.arg_num(&args, 2, 0.0)? as f32;
9948 let cz = self.arg_num(&args, 3, 0.0)? as f32;
9949 let ux = self.arg_num(&args, 4, 1.0)? as f32;
9950 let uy = self.arg_num(&args, 5, 0.0)? as f32;
9951 let uz = self.arg_num(&args, 6, 0.0)? as f32;
9952 let vx = self.arg_num(&args, 7, 0.0)? as f32;
9953 let vy = self.arg_num(&args, 8, 1.0)? as f32;
9954 let vz = self.arg_num(&args, 9, 0.0)? as f32;
9955 let size = self.arg_num(&args, 10, 1.0)? as f32;
9956 let s = self.arg_str(&args, 11, "");
9957 let fill_rows = self.arg_num(&args, 12, 0.0)? as i32;
9961 if id >= 0 && (id as usize) < self.fonts.len() && size > 0.0 {
9962 let font = &mut self.fonts[id as usize];
9964 let asc = font.ascent();
9965 let mut pen = 0.0f32;
9966 let mut lines: Vec<[f32; 6]> = Vec::new();
9967 for ch in s.chars() {
9968 let go = font.glyph_outline(ch, 0.01);
9969 let map = |p: [f32; 2], pen: f32| {
9970 let a = pen + p[0];
9971 let b = p[1] - asc; [
9973 cx + a * size * ux + b * size * vx,
9974 cy + a * size * uy + b * size * vy,
9975 cz + a * size * uz + b * size * vz,
9976 ]
9977 };
9978 for pl in &go.polylines {
9979 for seg in pl.windows(2) {
9980 let p0 = map(seg[0], pen);
9981 let p1 = map(seg[1], pen);
9982 lines.push([p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]]);
9983 }
9984 }
9985 if fill_rows > 0 {
9986 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
9990 for pl in &go.polylines {
9991 for p in pl {
9992 ymin = ymin.min(p[1]);
9993 ymax = ymax.max(p[1]);
9994 }
9995 }
9996 if ymax > ymin {
9997 for r in 0..fill_rows {
9998 let y =
9999 ymin + (r as f32 + 0.5) * (ymax - ymin) / fill_rows as f32;
10000 let mut xs: Vec<f32> = Vec::new();
10001 for pl in &go.polylines {
10002 let n = pl.len();
10003 if n < 2 {
10004 continue;
10005 }
10006 for k in 0..n {
10007 let p0 = pl[k];
10008 let p1 = pl[(k + 1) % n];
10009 if k + 1 == n
10010 && (p1[0] - p0[0]).abs() < 1e-6
10011 && (p1[1] - p0[1]).abs() < 1e-6
10012 {
10013 continue; }
10015 let (y0, y1) = (p0[1], p1[1]);
10016 if (y0 <= y && y1 > y) || (y1 <= y && y0 > y) {
10017 let t = (y - y0) / (y1 - y0);
10018 xs.push(p0[0] + t * (p1[0] - p0[0]));
10019 }
10020 }
10021 }
10022 xs.sort_by(|a, b| {
10023 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
10024 });
10025 let mut k = 0;
10026 while k + 1 < xs.len() {
10027 let a = map([xs[k], y], pen);
10028 let b = map([xs[k + 1], y], pen);
10029 lines.push([a[0], a[1], a[2], b[0], b[1], b[2]]);
10030 k += 2;
10031 }
10032 }
10033 }
10034 }
10035 pen += go.advance;
10036 }
10037 let mut gfx = self.gfx.borrow_mut();
10038 let color = gfx.color;
10039 let near = -gfx.camera.zdist + 0.05;
10040 for l in &lines {
10041 let (mut ax, mut ay, mut az) = (l[0], l[1], l[2]);
10042 let (mut bx, mut by, mut bz) = (l[3], l[4], l[5]);
10043 let da = gfx.camera.depth(ax, ay, az);
10044 let db = gfx.camera.depth(bx, by, bz);
10045 if da <= near && db <= near {
10046 continue;
10047 }
10048 if da <= near {
10049 let t = (near - da) / (db - da);
10050 ax += t * (bx - ax);
10051 ay += t * (by - ay);
10052 az += t * (bz - az);
10053 } else if db <= near {
10054 let t = (near - da) / (db - da);
10055 bx = ax + t * (bx - ax);
10056 by = ay + t * (by - ay);
10057 bz = az + t * (bz - az);
10058 }
10059 let (sax, say, da2) = gfx.camera.project(ax, ay, az);
10060 let (sbx, sby, db2) = gfx.camera.project(bx, by, bz);
10061 let depth = (da2 + db2) / 2.0;
10062 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
10063 }
10064 }
10065 return Ok(Value::Unit);
10066 },
10067 #[cfg(target_arch = "wasm32")]
10068 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" =>
10069 {
10070 return Ok(Value::Unit);
10071 },
10072 #[cfg(not(target_arch = "wasm32"))]
10074 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" =>
10075 {
10076 let id = self.arg_num(&args, 0, 0.0)? as i64;
10077 let px = self.arg_num(&args, 1, 16.0)? as f32;
10078 let s = self.arg_str(&args, 2, "");
10079 if id >= 0 && (id as usize) < self.fonts.len() {
10080 return Ok(Value::Number(self.fonts[id as usize].measure(&s, px) as f64));
10081 }
10082 return Ok(Value::Number(0.0));
10083 },
10084 #[cfg(target_arch = "wasm32")]
10085 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" =>
10086 {
10087 return Ok(Value::Number(0.0));
10088 },
10089 #[cfg(not(target_arch = "wasm32"))]
10094 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10095 | "フォント輪郭" | "글꼴윤곽" => {
10096 let id = self.arg_num(&args, 0, 0.0)? as i64;
10097 let s = self.arg_str(&args, 1, "");
10098 let tol = self.arg_num(&args, 2, 0.01)? as f32;
10099 let ch = s.chars().next().unwrap_or(' ');
10100 if id >= 0 && (id as usize) < self.fonts.len() {
10101 let go = self.fonts[id as usize].glyph_outline(ch, tol.max(1e-4));
10102 let mut contours: Vec<Value> = Vec::with_capacity(go.polylines.len());
10103 for pl in &go.polylines {
10104 let mut flat: Vec<Value> = Vec::with_capacity(pl.len() * 2);
10105 for p in pl {
10106 flat.push(Value::Number(p[0] as f64));
10107 flat.push(Value::Number(p[1] as f64));
10108 }
10109 contours.push(Value::List(Rc::new(flat)));
10110 }
10111 return Ok(Value::List(Rc::new(contours)));
10112 }
10113 return Ok(Value::List(Rc::new(vec![])));
10114 },
10115 #[cfg(target_arch = "wasm32")]
10116 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10117 | "フォント輪郭" | "글꼴윤곽" => {
10118 return Ok(Value::List(Rc::new(vec![])));
10119 },
10120 #[cfg(not(target_arch = "wasm32"))]
10123 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" => {
10124 let id = self.arg_num(&args, 0, 0.0)? as i64;
10125 let s = self.arg_str(&args, 1, "");
10126 let ch = s.chars().next().unwrap_or(' ');
10127 if id >= 0 && (id as usize) < self.fonts.len() {
10128 return Ok(Value::Number(self.fonts[id as usize].advance(ch) as f64));
10129 }
10130 return Ok(Value::Number(0.0));
10131 },
10132 #[cfg(target_arch = "wasm32")]
10133 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" => {
10134 return Ok(Value::Number(0.0));
10135 },
10136
10137 "ui_frame" | "边框" | "フレーム枠" | "프레임틀" | "กรอบ" => {
10139 let x = self.arg_num(&args, 0, 0.0)? as f32;
10140 let y = self.arg_num(&args, 1, 0.0)? as f32;
10141 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10142 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10143 let l = self.arg_num(&args, 4, 14.0)? as f32;
10144 let segs = ling_ui::holo::corner_brackets(x, y, w0, h0, l);
10145 let mut gfx = self.gfx.borrow_mut();
10146 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10147 for sg in segs {
10148 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10149 }
10150 return Ok(Value::Unit);
10151 },
10152 "ui_bevel" | "斜角框" | "ベベル枠" | "베벨틀" | "กรอบเฉียง" =>
10154 {
10155 let x = self.arg_num(&args, 0, 0.0)? as f32;
10156 let y = self.arg_num(&args, 1, 0.0)? as f32;
10157 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10158 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10159 let bv = self.arg_num(&args, 4, 10.0)? as f32;
10160 let segs = ling_ui::holo::beveled_rect(x, y, w0, h0, bv);
10161 let mut gfx = self.gfx.borrow_mut();
10162 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10163 for sg in segs {
10164 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10165 }
10166 return Ok(Value::Unit);
10167 },
10168
10169 #[cfg(not(target_arch = "wasm32"))]
10175 "ui_theme" | "界面主题" | "UIテーマ" | "인터페이스테마" | "ธีมส่วนติดต่อ" =>
10176 {
10177 let cur = self.ui_theme;
10178 let primary = self.color_at(&args, 0, cur.primary);
10179 let accent = self.color_at(&args, 3, cur.accent);
10180 let track = self.color_at(&args, 6, cur.track);
10181 let warn = self.color_at(&args, 9, cur.warn);
10182 let text = self.color_at(&args, 12, cur.text);
10183 let bg = self.color_at(&args, 15, cur.bg);
10184 self.ui_theme = UiTheme { primary, accent, track, warn, text, bg };
10185 return Ok(Value::Unit);
10186 },
10187
10188 "ui_theme_colors" | "인터페이스테마색상" => {
10194 let th = self.ui_theme;
10195 let mut out = Vec::with_capacity(18);
10196 for c in [th.primary, th.accent, th.track, th.warn, th.text, th.bg] {
10197 out.push(Value::Number(((c >> 16) & 0xFF) as f64));
10198 out.push(Value::Number(((c >> 8) & 0xFF) as f64));
10199 out.push(Value::Number((c & 0xFF) as f64));
10200 }
10201 return Ok(Value::List(Rc::new(out)));
10202 },
10203
10204 #[cfg(not(target_arch = "wasm32"))]
10206 "ui_radar" | "雷达" | "レーダー" | "레이더" | "เรดาร์" => {
10207 let cx = self.arg_num(&args, 0, 0.)? as f32;
10208 let cy = self.arg_num(&args, 1, 0.)? as f32;
10209 let r = self.arg_num(&args, 2, 60.)? as f32;
10210 let sweep = self.arg_num(&args, 3, 0.)? as f32;
10211 let th = self.ui_theme;
10212 let prim = self.color_at(&args, 4, th.primary);
10213 self.draw_ui(&ling_ui::widgets::radar(
10214 cx, cy, r, sweep, prim, th.accent, th.track,
10215 ));
10216 return Ok(Value::Unit);
10217 },
10218 #[cfg(not(target_arch = "wasm32"))]
10219 "ui_compass" | "罗盘" | "コンパス" | "나침반" | "เข็มทิศ" => {
10220 let x = self.arg_num(&args, 0, 0.)? as f32;
10221 let y = self.arg_num(&args, 1, 0.)? as f32;
10222 let w0 = self.arg_num(&args, 2, 300.)? as f32;
10223 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10224 let head = self.arg_num(&args, 4, 0.)? as f32;
10225 let th = self.ui_theme;
10226 let prim = self.color_at(&args, 5, th.primary);
10227 self.draw_ui(&ling_ui::widgets::compass(
10228 x, y, w0, h0, head, prim, th.track,
10229 ));
10230 return Ok(Value::Unit);
10231 },
10232 #[cfg(not(target_arch = "wasm32"))]
10233 "ui_reticle" | "准星" | "照準" | "조준선" | "เป้าเล็ง" => {
10234 let cx = self.arg_num(&args, 0, 0.)? as f32;
10235 let cy = self.arg_num(&args, 1, 0.)? as f32;
10236 let r = self.arg_num(&args, 2, 30.)? as f32;
10237 let spread = self.arg_num(&args, 3, 0.)? as f32;
10238 let th = self.ui_theme;
10239 let prim = self.color_at(&args, 4, th.primary);
10240 self.draw_ui(&ling_ui::widgets::reticle(cx, cy, r, spread, prim));
10241 return Ok(Value::Unit);
10242 },
10243 #[cfg(not(target_arch = "wasm32"))]
10244 "ui_target" | "锁定框" | "ターゲット" | "표적" | "กรอบเป้า" =>
10245 {
10246 let x = self.arg_num(&args, 0, 0.)? as f32;
10247 let y = self.arg_num(&args, 1, 0.)? as f32;
10248 let w0 = self.arg_num(&args, 2, 80.)? as f32;
10249 let h0 = self.arg_num(&args, 3, 80.)? as f32;
10250 let lock = self.arg_num(&args, 4, 0.)? as f32;
10251 let th = self.ui_theme;
10252 let prim = self.color_at(&args, 5, th.primary);
10253 self.draw_ui(&ling_ui::widgets::target(
10254 x, y, w0, h0, lock, prim, th.accent,
10255 ));
10256 return Ok(Value::Unit);
10257 },
10258 #[cfg(not(target_arch = "wasm32"))]
10259 "ui_panel" | "面板" | "パネル" | "패널" | "แผง" => {
10260 let x = self.arg_num(&args, 0, 0.)? as f32;
10261 let y = self.arg_num(&args, 1, 0.)? as f32;
10262 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10263 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10264 let bv = self.arg_num(&args, 4, 12.)? as f32;
10265 let th = self.ui_theme;
10266 let prim = self.color_at(&args, 5, th.primary);
10267 self.draw_ui(&ling_ui::widgets::panel(x, y, w0, h0, bv, prim, th.bg));
10268 return Ok(Value::Unit);
10269 },
10270 #[cfg(not(target_arch = "wasm32"))]
10271 "ui_scanlines" | "扫描线" | "走査線" | "스캔라인" | "เส้นสแกน" =>
10272 {
10273 let x = self.arg_num(&args, 0, 0.)? as f32;
10274 let y = self.arg_num(&args, 1, 0.)? as f32;
10275 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10276 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10277 let dens = self.arg_num(&args, 4, 24.)? as usize;
10278 let th = self.ui_theme;
10279 let line = self.color_at(&args, 5, th.track);
10280 self.draw_ui(&ling_ui::widgets::scanlines(x, y, w0, h0, dens, line));
10281 return Ok(Value::Unit);
10282 },
10283
10284 #[cfg(not(target_arch = "wasm32"))]
10286 "ui_bar" | "进度条" | "バー" | "막대" | "แถบ" => {
10287 let x = self.arg_num(&args, 0, 0.)? as f32;
10288 let y = self.arg_num(&args, 1, 0.)? as f32;
10289 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10290 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10291 let val = self.arg_num(&args, 4, 0.)? as f32;
10292 let max = self.arg_num(&args, 5, 1.)? as f32;
10293 let th = self.ui_theme;
10294 let fill = self.color_at(&args, 6, th.primary);
10295 self.draw_ui(&ling_ui::widgets::bar(
10296 x,
10297 y,
10298 w0,
10299 h0,
10300 val / max.max(1e-6),
10301 fill,
10302 th.track,
10303 ));
10304 return Ok(Value::Unit);
10305 },
10306 #[cfg(not(target_arch = "wasm32"))]
10307 "ui_segbar" | "分段条" | "分割バー" | "분할막대" | "แถบแบ่ง" =>
10308 {
10309 let x = self.arg_num(&args, 0, 0.)? as f32;
10310 let y = self.arg_num(&args, 1, 0.)? as f32;
10311 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10312 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10313 let val = self.arg_num(&args, 4, 0.)? as f32;
10314 let max = self.arg_num(&args, 5, 1.)? as f32;
10315 let segs = self.arg_num(&args, 6, 10.)? as usize;
10316 let th = self.ui_theme;
10317 let fill = self.color_at(&args, 7, th.primary);
10318 self.draw_ui(&ling_ui::widgets::segbar(
10319 x,
10320 y,
10321 w0,
10322 h0,
10323 val / max.max(1e-6),
10324 segs,
10325 fill,
10326 th.track,
10327 ));
10328 return Ok(Value::Unit);
10329 },
10330 #[cfg(not(target_arch = "wasm32"))]
10331 "ui_gauge" | "仪表" | "ゲージ" | "게이지" | "มาตรวัด" => {
10332 let cx = self.arg_num(&args, 0, 0.)? as f32;
10333 let cy = self.arg_num(&args, 1, 0.)? as f32;
10334 let r = self.arg_num(&args, 2, 50.)? as f32;
10335 let val = self.arg_num(&args, 3, 0.)? as f32;
10336 let max = self.arg_num(&args, 4, 1.)? as f32;
10337 let th = self.ui_theme;
10338 let needle = self.color_at(&args, 5, th.warn);
10339 self.draw_ui(&ling_ui::widgets::gauge(
10340 cx,
10341 cy,
10342 r,
10343 val / max.max(1e-6),
10344 needle,
10345 th.accent,
10346 th.track,
10347 ));
10348 return Ok(Value::Unit);
10349 },
10350 #[cfg(not(target_arch = "wasm32"))]
10351 "ui_ring" | "环表" | "リングメーター" | "링미터" | "วงแหวนวัด" =>
10352 {
10353 let cx = self.arg_num(&args, 0, 0.)? as f32;
10354 let cy = self.arg_num(&args, 1, 0.)? as f32;
10355 let r = self.arg_num(&args, 2, 40.)? as f32;
10356 let val = self.arg_num(&args, 3, 0.)? as f32;
10357 let max = self.arg_num(&args, 4, 1.)? as f32;
10358 let th = self.ui_theme;
10359 let fill = self.color_at(&args, 5, th.primary);
10360 self.draw_ui(&ling_ui::widgets::ring(
10361 cx,
10362 cy,
10363 r,
10364 val / max.max(1e-6),
10365 fill,
10366 th.track,
10367 ));
10368 return Ok(Value::Unit);
10369 },
10370 #[cfg(not(target_arch = "wasm32"))]
10371 "ui_vu" | "音量条" | "VUメーター" | "음량막대" | "มาตรเสียง" =>
10372 {
10373 let x = self.arg_num(&args, 0, 0.)? as f32;
10374 let y = self.arg_num(&args, 1, 0.)? as f32;
10375 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10376 let h0 = self.arg_num(&args, 3, 60.)? as f32;
10377 let levels = self.arg_list_f32(&args, 4);
10378 let th = self.ui_theme;
10379 let fill = self.color_at(&args, 5, th.primary);
10380 self.draw_ui(&ling_ui::widgets::vu(x, y, w0, h0, &levels, fill, th.warn));
10381 return Ok(Value::Unit);
10382 },
10383 #[cfg(not(target_arch = "wasm32"))]
10384 "ui_spark" | "迷你图" | "スパークライン" | "스파크라인" | "กราฟจิ๋ว" =>
10385 {
10386 let x = self.arg_num(&args, 0, 0.)? as f32;
10387 let y = self.arg_num(&args, 1, 0.)? as f32;
10388 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10389 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10390 let vals = self.arg_list_f32(&args, 4);
10391 let th = self.ui_theme;
10392 let line = self.color_at(&args, 5, th.accent);
10393 self.draw_ui(&ling_ui::widgets::spark(x, y, w0, h0, &vals, line));
10394 return Ok(Value::Unit);
10395 },
10396 #[cfg(not(target_arch = "wasm32"))]
10397 "ui_battery" | "电池" | "バッテリー" | "배터리" | "แบตเตอรี่" =>
10398 {
10399 let x = self.arg_num(&args, 0, 0.)? as f32;
10400 let y = self.arg_num(&args, 1, 0.)? as f32;
10401 let w0 = self.arg_num(&args, 2, 50.)? as f32;
10402 let h0 = self.arg_num(&args, 3, 22.)? as f32;
10403 let val = self.arg_num(&args, 4, 1.)? as f32;
10404 let max = self.arg_num(&args, 5, 1.)? as f32;
10405 let th = self.ui_theme;
10406 let fill = self.color_at(&args, 6, th.accent);
10407 self.draw_ui(&ling_ui::widgets::battery(
10408 x,
10409 y,
10410 w0,
10411 h0,
10412 val / max.max(1e-6),
10413 fill,
10414 th.track,
10415 th.warn,
10416 ));
10417 return Ok(Value::Unit);
10418 },
10419
10420 #[cfg(not(target_arch = "wasm32"))]
10422 "ui_button" | "按钮" | "ボタン" | "버튼" | "ปุ่ม" => {
10423 let x = self.arg_num(&args, 0, 0.)? as f32;
10424 let y = self.arg_num(&args, 1, 0.)? as f32;
10425 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10426 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10427 let (mx, my, down) = self.mouse_now();
10428 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10429 let clicked = hover && down && !self.mouse_was_down;
10430 let th = self.ui_theme;
10431 let prim = self.color_at(&args, 4, th.primary);
10432 self.draw_ui(&ling_ui::widgets::button(
10433 x,
10434 y,
10435 w0,
10436 h0,
10437 hover,
10438 down && hover,
10439 prim,
10440 th.bg,
10441 ));
10442 return Ok(Value::Number(if clicked { 1.0 } else { 0.0 }));
10443 },
10444 #[cfg(not(target_arch = "wasm32"))]
10445 "ui_toggle" | "开关" | "トグル" | "토글" | "สวิตช์" => {
10446 let x = self.arg_num(&args, 0, 0.)? as f32;
10447 let y = self.arg_num(&args, 1, 0.)? as f32;
10448 let w0 = self.arg_num(&args, 2, 52.)? as f32;
10449 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10450 let mut state = self.arg_num(&args, 4, 0.)? > 0.5;
10451 let (mx, my, down) = self.mouse_now();
10452 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10453 if hover && down && !self.mouse_was_down {
10454 state = !state;
10455 }
10456 let th = self.ui_theme;
10457 let on = self.color_at(&args, 5, th.accent);
10458 self.draw_ui(&ling_ui::widgets::toggle(x, y, w0, h0, state, on, th.track));
10459 return Ok(Value::Number(if state { 1.0 } else { 0.0 }));
10460 },
10461 #[cfg(not(target_arch = "wasm32"))]
10462 "ui_slider" | "滑块" | "スライダー" | "슬라이더" | "แถบเลื่อน" =>
10463 {
10464 let x = self.arg_num(&args, 0, 0.)? as f32;
10465 let y = self.arg_num(&args, 1, 0.)? as f32;
10466 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10467 let mut val = self.arg_num(&args, 3, 0.)? as f32;
10468 let mn = self.arg_num(&args, 4, 0.)? as f32;
10469 let mx_ = self.arg_num(&args, 5, 1.)? as f32;
10470 let (mx, my, down) = self.mouse_now();
10471 let hover = ling_ui::holo::hit_rect(mx, my, x - 8.0, y - 10.0, w0 + 16.0, 20.0);
10472 if hover && down {
10473 let frac = ((mx - x) / w0).clamp(0.0, 1.0);
10474 val = mn + (mx_ - mn) * frac;
10475 }
10476 let frac = ((val - mn) / (mx_ - mn).abs().max(1e-6)).clamp(0.0, 1.0);
10477 let th = self.ui_theme;
10478 let fill = self.color_at(&args, 6, th.primary);
10479 self.draw_ui(&ling_ui::widgets::slider(
10480 x, y, w0, frac, hover, fill, th.track,
10481 ));
10482 return Ok(Value::Number(val as f64));
10483 },
10484 #[cfg(not(target_arch = "wasm32"))]
10485 "ui_checkbox" | "复选框" | "チェックボックス" | "체크박스" | "ช่องเลือก" =>
10486 {
10487 let x = self.arg_num(&args, 0, 0.)? as f32;
10488 let y = self.arg_num(&args, 1, 0.)? as f32;
10489 let s = self.arg_num(&args, 2, 20.)? as f32;
10490 let mut checked = self.arg_num(&args, 3, 0.)? > 0.5;
10491 let (mx, my, down) = self.mouse_now();
10492 let hover = ling_ui::holo::hit_rect(mx, my, x, y, s, s);
10493 if hover && down && !self.mouse_was_down {
10494 checked = !checked;
10495 }
10496 let th = self.ui_theme;
10497 let prim = self.color_at(&args, 4, th.primary);
10498 self.draw_ui(&ling_ui::widgets::checkbox(
10499 x, y, s, checked, hover, prim, th.track,
10500 ));
10501 return Ok(Value::Number(if checked { 1.0 } else { 0.0 }));
10502 },
10503 #[cfg(not(target_arch = "wasm32"))]
10504 "ui_tabs" | "标签页" | "タブ" | "탭" | "แท็บ" => {
10505 let x = self.arg_num(&args, 0, 0.)? as f32;
10506 let y = self.arg_num(&args, 1, 0.)? as f32;
10507 let w0 = self.arg_num(&args, 2, 240.)? as f32;
10508 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10509 let count = self.arg_num(&args, 4, 3.)? as usize;
10510 let mut active = self.arg_num(&args, 5, 0.)? as i32;
10511 let (mx, my, down) = self.mouse_now();
10512 let mut hover = -1;
10513 if my >= y && my <= y + h0 && mx >= x && mx <= x + w0 && count > 0 {
10514 hover = (((mx - x) / (w0 / count as f32)) as i32)
10515 .max(0)
10516 .min(count as i32 - 1);
10517 if down && !self.mouse_was_down {
10518 active = hover;
10519 }
10520 }
10521 let th = self.ui_theme;
10522 let prim = self.color_at(&args, 6, th.primary);
10523 self.draw_ui(&ling_ui::widgets::tabs(
10524 x,
10525 y,
10526 w0,
10527 h0,
10528 count,
10529 active as usize,
10530 hover,
10531 prim,
10532 th.track,
10533 ));
10534 return Ok(Value::Number(active as f64));
10535 },
10536 #[cfg(not(target_arch = "wasm32"))]
10537 "ui_progress" | "进度" | "プログレス" | "진행바" | "ความคืบหน้า" =>
10538 {
10539 let x = self.arg_num(&args, 0, 0.)? as f32;
10540 let y = self.arg_num(&args, 1, 0.)? as f32;
10541 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10542 let h0 = self.arg_num(&args, 3, 12.)? as f32;
10543 let frac = self.arg_num(&args, 4, 0.)? as f32;
10544 let th = self.ui_theme;
10545 let fill = self.color_at(&args, 5, th.accent);
10546 self.draw_ui(&ling_ui::widgets::progress(
10547 x, y, w0, h0, frac, fill, th.track,
10548 ));
10549 return Ok(Value::Unit);
10550 },
10551 #[cfg(not(target_arch = "wasm32"))]
10552 "ui_tooltip" | "提示框" | "ツールチップ" | "툴팁" | "คำแนะนำ" =>
10553 {
10554 let x = self.arg_num(&args, 0, 0.)? as f32;
10555 let y = self.arg_num(&args, 1, 0.)? as f32;
10556 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10557 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10558 let th = self.ui_theme;
10559 let prim = self.color_at(&args, 4, th.primary);
10560 self.draw_ui(&ling_ui::widgets::tooltip(x, y, w0, h0, prim, th.bg));
10561 return Ok(Value::Unit);
10562 },
10563 #[cfg(not(target_arch = "wasm32"))]
10564 "ui_stepper" | "步进器" | "ステッパー" | "스테퍼" | "ตัวปรับค่า" =>
10565 {
10566 let x = self.arg_num(&args, 0, 0.)? as f32;
10567 let y = self.arg_num(&args, 1, 0.)? as f32;
10568 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10569 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10570 let mut val = self.arg_num(&args, 4, 0.)? as f32;
10571 let step = self.arg_num(&args, 5, 1.)? as f32;
10572 let (mx, my, down) = self.mouse_now();
10573 let hm = ling_ui::holo::hit_rect(mx, my, x, y, h0, h0);
10574 let hp = ling_ui::holo::hit_rect(mx, my, x + w0 - h0, y, h0, h0);
10575 if down && !self.mouse_was_down {
10576 if hm {
10577 val -= step;
10578 }
10579 if hp {
10580 val += step;
10581 }
10582 }
10583 let th = self.ui_theme;
10584 let prim = self.color_at(&args, 6, th.primary);
10585 self.draw_ui(&ling_ui::widgets::stepper(
10586 x, y, w0, h0, hm, hp, prim, th.track,
10587 ));
10588 return Ok(Value::Number(val as f64));
10589 },
10590
10591 #[cfg(not(target_arch = "wasm32"))]
10593 "ui_healthbar" | "血条" | "体力バー" | "체력바" | "แถบพลังชีวิต" =>
10594 {
10595 let x = self.arg_num(&args, 0, 0.)? as f32;
10596 let y = self.arg_num(&args, 1, 0.)? as f32;
10597 let w0 = self.arg_num(&args, 2, 180.)? as f32;
10598 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10599 let val = self.arg_num(&args, 4, 1.)? as f32;
10600 let max = self.arg_num(&args, 5, 1.)? as f32;
10601 let pulse = self.arg_num(&args, 6, 0.)? as f32;
10602 let th = self.ui_theme;
10603 let full = self.color_at(&args, 7, th.accent);
10604 self.draw_ui(&ling_ui::widgets::healthbar(
10605 x,
10606 y,
10607 w0,
10608 h0,
10609 val / max.max(1e-6),
10610 pulse,
10611 full,
10612 th.warn,
10613 th.track,
10614 ));
10615 return Ok(Value::Unit);
10616 },
10617 #[cfg(not(target_arch = "wasm32"))]
10618 "ui_cooldown" | "冷却" | "クールダウン" | "쿨다운" | "คูลดาวน์" =>
10619 {
10620 let cx = self.arg_num(&args, 0, 0.)? as f32;
10621 let cy = self.arg_num(&args, 1, 0.)? as f32;
10622 let r = self.arg_num(&args, 2, 28.)? as f32;
10623 let frac = self.arg_num(&args, 3, 0.)? as f32;
10624 let th = self.ui_theme;
10625 let fill = self.color_at(&args, 4, th.primary);
10626 self.draw_ui(&ling_ui::widgets::cooldown(cx, cy, r, frac, fill, th.track));
10627 return Ok(Value::Unit);
10628 },
10629 #[cfg(not(target_arch = "wasm32"))]
10630 "ui_counter" | "计数器" | "カウンター" | "카운터" | "ตัวนับ" => {
10631 let x = self.arg_num(&args, 0, 0.)? as f32;
10632 let y = self.arg_num(&args, 1, 0.)? as f32;
10633 let dw = self.arg_num(&args, 2, 14.)? as f32;
10634 let dh = self.arg_num(&args, 3, 24.)? as f32;
10635 let val = self.arg_num(&args, 4, 0.)? as i64;
10636 let digits = self.arg_num(&args, 5, 4.)? as usize;
10637 let th = self.ui_theme;
10638 let on = self.color_at(&args, 6, th.primary);
10639 let off = ling_ui::widgets::shade(th.track, 0.5);
10640 self.draw_ui(&ling_ui::widgets::counter(
10641 x, y, dw, dh, val, digits, on, off,
10642 ));
10643 return Ok(Value::Unit);
10644 },
10645 #[cfg(not(target_arch = "wasm32"))]
10646 "ui_minimap" | "小地图" | "ミニマップ" | "미니맵" | "แผนที่ย่อ" =>
10647 {
10648 let x = self.arg_num(&args, 0, 0.)? as f32;
10649 let y = self.arg_num(&args, 1, 0.)? as f32;
10650 let w0 = self.arg_num(&args, 2, 140.)? as f32;
10651 let h0 = self.arg_num(&args, 3, 140.)? as f32;
10652 let th = self.ui_theme;
10653 let prim = self.color_at(&args, 4, th.primary);
10654 self.draw_ui(&ling_ui::widgets::minimap(x, y, w0, h0, prim, th.bg));
10655 return Ok(Value::Unit);
10656 },
10657 #[cfg(not(target_arch = "wasm32"))]
10658 "ui_dpad" | "方向键" | "方向パッド" | "방향패드" | "ปุ่มทิศทาง" =>
10659 {
10660 let cx = self.arg_num(&args, 0, 0.)? as f32;
10661 let cy = self.arg_num(&args, 1, 0.)? as f32;
10662 let r = self.arg_num(&args, 2, 50.)? as f32;
10663 let (mx, my, down) = self.mouse_now();
10664 let mut dir = 0;
10665 if down {
10666 let (dx, dy) = (mx - cx, my - cy);
10667 if dx * dx + dy * dy <= r * r {
10668 if dx.abs() > dy.abs() {
10669 dir = if dx > 0.0 { 2 } else { 4 };
10670 } else {
10671 dir = if dy > 0.0 { 3 } else { 1 };
10672 }
10673 }
10674 }
10675 let th = self.ui_theme;
10676 let prim = self.color_at(&args, 3, th.primary);
10677 self.draw_ui(&ling_ui::widgets::dpad(cx, cy, r, dir, prim, th.track));
10678 return Ok(Value::Number(dir as f64));
10679 },
10680 #[cfg(not(target_arch = "wasm32"))]
10681 "ui_slotgrid" | "物品格" | "スロットグリッド" | "슬롯격자" | "ช่องไอเทม" =>
10682 {
10683 let x = self.arg_num(&args, 0, 0.)? as f32;
10684 let y = self.arg_num(&args, 1, 0.)? as f32;
10685 let cols = self.arg_num(&args, 2, 4.)? as usize;
10686 let rows = self.arg_num(&args, 3, 1.)? as usize;
10687 let cell = self.arg_num(&args, 4, 36.)? as f32;
10688 let sel = self.arg_num(&args, 5, -1.)? as i32;
10689 let th = self.ui_theme;
10690 let prim = self.color_at(&args, 6, th.primary);
10691 self.draw_ui(&ling_ui::widgets::slotgrid(
10692 x, y, cols, rows, cell, sel, prim, th.track,
10693 ));
10694 return Ok(Value::Unit);
10695 },
10696 #[cfg(not(target_arch = "wasm32"))]
10697 "ui_vignette" | "暗角" | "ビネット" | "비네트" | "ขอบมืด" => {
10698 let intensity = self.arg_num(&args, 0, 0.5)? as f32;
10699 let (w, h) = {
10700 let g = self.gfx.borrow();
10701 (g.width as f32, g.height as f32)
10702 };
10703 let th = self.ui_theme;
10704 let col = self.color_at(&args, 1, th.warn);
10705 self.draw_ui(&ling_ui::widgets::vignette(w, h, intensity, col));
10706 return Ok(Value::Unit);
10707 },
10708
10709 #[cfg(not(target_arch = "wasm32"))]
10711 "ui_gauge3d" | "立体仪表" | "立体ゲージ" | "입체게이지" | "มาตรวัด3มิติ" =>
10712 {
10713 let cx = self.arg_num(&args, 0, 0.)? as f32;
10714 let cy = self.arg_num(&args, 1, 0.)? as f32;
10715 let r = self.arg_num(&args, 2, 50.)? as f32;
10716 let val = self.arg_num(&args, 3, 0.)? as f32;
10717 let max = self.arg_num(&args, 4, 1.)? as f32;
10718 let spin = self.arg_num(&args, 5, 0.)? as f32;
10719 let th = self.ui_theme;
10720 let fill = self.color_at(&args, 6, th.primary);
10721 self.draw_ui(&ling_ui::widgets::gauge3d(
10722 cx,
10723 cy,
10724 r,
10725 val / max.max(1e-6),
10726 spin,
10727 fill,
10728 th.track,
10729 ));
10730 return Ok(Value::Unit);
10731 },
10732 #[cfg(not(target_arch = "wasm32"))]
10733 "ui_panel3d" | "立体面板" | "立体パネル" | "입체패널" | "แผง3มิติ" =>
10734 {
10735 let x = self.arg_num(&args, 0, 0.)? as f32;
10736 let y = self.arg_num(&args, 1, 0.)? as f32;
10737 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10738 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10739 let depth = self.arg_num(&args, 4, 14.)? as f32;
10740 let th = self.ui_theme;
10741 let prim = self.color_at(&args, 5, th.primary);
10742 self.draw_ui(&ling_ui::widgets::panel3d(x, y, w0, h0, depth, prim, th.bg));
10743 return Ok(Value::Unit);
10744 },
10745 #[cfg(not(target_arch = "wasm32"))]
10746 "ui_radar3d" | "立体雷达" | "立体レーダー" | "입체레이더" | "เรดาร์3มิติ" =>
10747 {
10748 let cx = self.arg_num(&args, 0, 0.)? as f32;
10749 let cy = self.arg_num(&args, 1, 0.)? as f32;
10750 let r = self.arg_num(&args, 2, 60.)? as f32;
10751 let tilt = self.arg_num(&args, 3, 0.9)? as f32;
10752 let sweep = self.arg_num(&args, 4, 0.)? as f32;
10753 let th = self.ui_theme;
10754 let prim = self.color_at(&args, 5, th.primary);
10755 self.draw_ui(&ling_ui::widgets::radar3d(
10756 cx, cy, r, tilt, sweep, prim, th.track,
10757 ));
10758 return Ok(Value::Unit);
10759 },
10760
10761 #[cfg(not(target_arch = "wasm32"))]
10763 "audio_blip" | "提示音" | "ビープ音" | "효과음" | "เสียงบี๊บ" =>
10764 {
10765 let freq = self.arg_num(&args, 0, 660.)? as f32;
10766 let dur = self.arg_num(&args, 1, 0.08)? as f32;
10767 let wave = Wave::from_name(&self.arg_str(&args, 2, "sine"));
10768 let amp = self.arg_num(&args, 3, 0.25)? as f32;
10769 if let Some(audio) = &self.audio {
10770 audio.blip(freq, amp, dur, wave);
10771 }
10772 return Ok(Value::Unit);
10773 },
10774 #[cfg(not(target_arch = "wasm32"))]
10775 "ui_sound" | "界面音" | "UI音" | "인터페이스음" | "เสียงปุ่ม" =>
10776 {
10777 let name = self.arg_str(&args, 0, "click");
10778 if let Some(audio) = &self.audio {
10779 match name.as_str() {
10780 "hover" => audio.blip(880.0, 0.10, 0.04, Wave::Sine),
10781 "confirm" => {
10782 audio.blip(660.0, 0.22, 0.07, Wave::Square);
10783 audio.blip(990.0, 0.18, 0.10, Wave::Square);
10784 },
10785 "error" => {
10786 audio.blip(180.0, 0.30, 0.16, Wave::Saw);
10787 audio.blip(140.0, 0.30, 0.18, Wave::Saw);
10788 },
10789 "toggle" => audio.blip(520.0, 0.22, 0.05, Wave::Triangle),
10790 "tick" => audio.blip(1500.0, 0.12, 0.02, Wave::Square),
10791 _ => audio.blip(720.0, 0.26, 0.05, Wave::Square), }
10793 }
10794 return Ok(Value::Unit);
10795 },
10796
10797 #[cfg(not(target_arch = "wasm32"))]
10805 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" =>
10806 {
10807 let path = self.arg_str(&args, 0, "");
10808 let resolved = if std::path::Path::new(&path).exists() {
10809 path.clone()
10810 } else if let Some(d) = &self.source_dir {
10811 d.join(&path).to_string_lossy().into_owned()
10812 } else {
10813 path.clone()
10814 };
10815 match ling_music::load(&resolved) {
10816 Ok(t) => {
10817 let id = self.tracks.len();
10818 self.tracks.push(t);
10819 return Ok(Value::Number(id as f64));
10820 },
10821 Err(e) => {
10822 eprintln!("music_load failed ({path}): {e}");
10823 return Ok(Value::Number(-1.0));
10824 },
10825 }
10826 },
10827 #[cfg(not(target_arch = "wasm32"))]
10828 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" =>
10829 {
10830 let id = self.arg_num(&args, 0, 0.0)? as i64;
10831 let d = self
10832 .tracks
10833 .get(id as usize)
10834 .map(|t| t.duration)
10835 .unwrap_or(0.0);
10836 return Ok(Value::Number(d as f64));
10837 },
10838 #[cfg(not(target_arch = "wasm32"))]
10839 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" =>
10840 {
10841 let id = self.arg_num(&args, 0, 0.0)? as i64;
10842 let b = self
10843 .tracks
10844 .get(id as usize)
10845 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
10846 .unwrap_or(0.0);
10847 return Ok(Value::Number(b as f64));
10848 },
10849 #[cfg(not(target_arch = "wasm32"))]
10850 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" => {
10851 let id = self.arg_num(&args, 0, 0.0)? as i64;
10852 let k = self
10853 .tracks
10854 .get(id as usize)
10855 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
10856 .unwrap_or_default();
10857 return Ok(Value::Str(k));
10858 },
10859 #[cfg(not(target_arch = "wasm32"))]
10860 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" =>
10861 {
10862 let id = self.arg_num(&args, 0, 0.0)? as i64;
10863 let v = self
10864 .tracks
10865 .get(id as usize)
10866 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
10867 .unwrap_or_default();
10868 return Ok(Value::List(Rc::new(
10869 v.into_iter().map(|x| Value::Number(x as f64)).collect(),
10870 )));
10871 },
10872 #[cfg(not(target_arch = "wasm32"))]
10873 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" =>
10874 {
10875 let id = self.arg_num(&args, 0, 0.0)? as i64;
10876 let beats = self
10877 .tracks
10878 .get(id as usize)
10879 .map(|t| {
10880 let b = ling_music::analysis::bpm(&t.mono, t.rate);
10881 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
10882 })
10883 .unwrap_or_default();
10884 return Ok(Value::List(Rc::new(
10885 beats.into_iter().map(|x| Value::Number(x as f64)).collect(),
10886 )));
10887 },
10888
10889 #[cfg(not(target_arch = "wasm32"))]
10891 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" =>
10892 {
10893 let id = self.arg_num(&args, 0, 0.0)? as i64;
10894 if self.ensure_music() {
10895 let track = self
10896 .tracks
10897 .get(id as usize)
10898 .map(|t| (t.stereo.clone(), t.rate));
10899 if let (Some((st, rate)), Some(m)) = (track, &self.music) {
10900 m.set_track(st, rate);
10901 m.play();
10902 } else if let Some(m) = &self.music {
10903 m.play();
10904 }
10905 }
10906 return Ok(Value::Unit);
10907 },
10908 #[cfg(not(target_arch = "wasm32"))]
10909 "music_pause" | "暂停音乐" | "音楽一時停止" | "음악일시정지" | "หยุดเพลงชั่วคราว" =>
10910 {
10911 if let Some(m) = &self.music {
10912 m.pause();
10913 }
10914 return Ok(Value::Unit);
10915 },
10916 #[cfg(not(target_arch = "wasm32"))]
10917 "music_stop" | "停止音乐" | "音楽停止" | "음악정지" | "หยุดเพลง" =>
10918 {
10919 if let Some(m) = &self.music {
10920 m.stop();
10921 }
10922 return Ok(Value::Unit);
10923 },
10924 #[cfg(not(target_arch = "wasm32"))]
10925 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" =>
10926 {
10927 let sec = self.arg_num(&args, 0, 0.0)? as f32;
10928 if let Some(m) = &self.music {
10929 m.seek(sec);
10930 }
10931 return Ok(Value::Unit);
10932 },
10933 #[cfg(not(target_arch = "wasm32"))]
10934 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" =>
10935 {
10936 let p = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
10937 return Ok(Value::Number(p as f64));
10938 },
10939 #[cfg(not(target_arch = "wasm32"))]
10940 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" =>
10941 {
10942 let v = self.arg_num(&args, 0, 0.8)? as f32;
10943 if self.ensure_music() {
10944 if let Some(m) = &self.music {
10945 m.set_volume(v);
10946 }
10947 }
10948 return Ok(Value::Unit);
10949 },
10950
10951 #[cfg(not(target_arch = "wasm32"))]
10953 "music_patch" | "乐器音色" | "音色読込" | "악기패치" | "แพตช์เครื่องดนตรี" =>
10954 {
10955 let path = self.arg_str(&args, 0, "");
10956 let resolved = if std::path::Path::new(&path).exists() {
10957 path.clone()
10958 } else if let Some(d) = &self.source_dir {
10959 d.join(&path).to_string_lossy().into_owned()
10960 } else {
10961 path.clone()
10962 };
10963 if !self.ensure_music() {
10964 return Ok(Value::Number(-1.0));
10965 }
10966 match ling_music::patch::from_path(&resolved) {
10967 Ok(p) => {
10968 let id = self.music.as_ref().unwrap().add_patch(p);
10969 return Ok(Value::Number(id as f64));
10970 },
10971 Err(e) => {
10972 eprintln!("music_patch failed ({path}): {e}");
10973 return Ok(Value::Number(-1.0));
10974 },
10975 }
10976 },
10977 #[cfg(not(target_arch = "wasm32"))]
10978 "music_note" | "弹音符" | "音符演奏" | "음표연주" | "เล่นโน้ต" =>
10979 {
10980 let inst = self.arg_num(&args, 0, 0.0)? as usize;
10981 let midi = self.pitch_arg(&args, 1, 60);
10982 let dur = self.arg_num(&args, 2, 0.5)? as f32;
10983 let vel = self.arg_num(&args, 3, 0.9)? as f32;
10984 if self.ensure_music() {
10985 if let Some(m) = &self.music {
10986 m.note(inst, midi, vel, dur);
10987 }
10988 }
10989 return Ok(Value::Unit);
10990 },
10991 #[cfg(not(target_arch = "wasm32"))]
10992 "music_note_on" | "音符开始" | "音符オン" | "음표켜기" | "โน้ตเริ่ม" =>
10993 {
10994 let inst = self.arg_num(&args, 0, 0.0)? as usize;
10995 let midi = self.pitch_arg(&args, 1, 60);
10996 let vel = self.arg_num(&args, 2, 0.9)? as f32;
10997 if self.ensure_music() {
10998 if let Some(m) = &self.music {
10999 m.note_on(inst, midi, vel);
11000 }
11001 }
11002 return Ok(Value::Unit);
11003 },
11004 #[cfg(not(target_arch = "wasm32"))]
11005 "music_note_off" | "音符结束" | "音符オフ" | "음표끄기" | "โน้ตจบ" =>
11006 {
11007 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11008 let midi = self.pitch_arg(&args, 1, 60);
11009 if let Some(m) = &self.music {
11010 m.note_off(inst, midi);
11011 }
11012 return Ok(Value::Unit);
11013 },
11014
11015 #[cfg(not(target_arch = "wasm32"))]
11017 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" =>
11018 {
11019 let delta_ms = self.arg_num(&args, 0, 9999.0)? as f32;
11020 return Ok(Value::Number(
11021 ling_music::Grade::judge(delta_ms).index() as f64
11022 ));
11023 },
11024 #[cfg(not(target_arch = "wasm32"))]
11025 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" =>
11026 {
11027 let idx = self.arg_num(&args, 0, 4.0)? as i32;
11028 return Ok(Value::Str(
11029 ling_music::Grade::from_index(idx).name().to_string(),
11030 ));
11031 },
11032
11033 #[cfg(not(target_arch = "wasm32"))]
11035 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" =>
11036 {
11037 let path = self.arg_str(&args, 0, "");
11038 let resolved = if std::path::Path::new(&path).exists() {
11039 path.clone()
11040 } else if let Some(d) = &self.source_dir {
11041 d.join(&path).to_string_lossy().into_owned()
11042 } else {
11043 path.clone()
11044 };
11045 match std::fs::read_to_string(&resolved) {
11046 Ok(text) => {
11047 let id = self.lyrics.len();
11048 self.lyrics.push(ling_music::Lyrics::parse(&text));
11049 return Ok(Value::Number(id as f64));
11050 },
11051 Err(e) => {
11052 eprintln!("music_lrc failed ({path}): {e}");
11053 return Ok(Value::Number(-1.0));
11054 },
11055 }
11056 },
11057 #[cfg(not(target_arch = "wasm32"))]
11058 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" =>
11059 {
11060 let id = self.arg_num(&args, 0, 0.0)? as i64;
11061 let t = self.arg_num(&args, 1, 0.0)? as f32;
11062 let line = self
11063 .lyrics
11064 .get(id as usize)
11065 .map(|l| l.line_at(t).to_string())
11066 .unwrap_or_default();
11067 return Ok(Value::Str(line));
11068 },
11069 #[cfg(not(target_arch = "wasm32"))]
11070 "music_mic_pitch" | "麦克风音高" | "マイク音程" | "마이크음정" | "ระดับเสียงไมค์" =>
11071 {
11072 let hz = if let Some(mic) = self.mic.as_ref() {
11073 let s = mic.latest_samples();
11074 let rate = mic.sample_rate();
11075 ling_music::pitch::detect(&s, rate).unwrap_or(0.0)
11076 } else {
11077 0.0
11078 };
11079 return Ok(Value::Number(hz as f64));
11080 },
11081 #[cfg(not(target_arch = "wasm32"))]
11082 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" =>
11083 {
11084 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11085 return Ok(Value::Str(ling_music::note::hz_to_name(hz)));
11086 },
11087 #[cfg(not(target_arch = "wasm32"))]
11088 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" =>
11089 {
11090 let midi = self.pitch_arg(&args, 0, 69);
11091 return Ok(Value::Number(
11092 ling_music::note::midi_to_hz(midi as f32) as f64
11093 ));
11094 },
11095 #[cfg(not(target_arch = "wasm32"))]
11096 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" =>
11097 {
11098 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11099 let target = self.arg_num(&args, 1, 0.0)? as f32;
11100 return Ok(Value::Number(
11101 ling_music::karaoke::pitch_score(hz, target) as f64
11102 ));
11103 },
11104
11105 #[cfg(not(target_arch = "wasm32"))]
11107 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" =>
11108 {
11109 let path = self.arg_str(&args, 0, "");
11110 let resolved = if std::path::Path::new(&path).exists() {
11111 path.clone()
11112 } else if let Some(d) = &self.source_dir {
11113 d.join(&path).to_string_lossy().into_owned()
11114 } else {
11115 path.clone()
11116 };
11117 match ling_music::midi::load(&resolved) {
11118 Ok(m) => {
11119 let id = self.midis.len();
11120 self.midis.push(m);
11121 return Ok(Value::Number(id as f64));
11122 },
11123 Err(e) => {
11124 eprintln!("music_midi_load failed ({path}): {e}");
11125 return Ok(Value::Number(-1.0));
11126 },
11127 }
11128 },
11129 #[cfg(not(target_arch = "wasm32"))]
11130 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" =>
11131 {
11132 let id = self.arg_num(&args, 0, 0.0)? as i64;
11133 let n = self
11134 .midis
11135 .get(id as usize)
11136 .map(|m| m.notes.len())
11137 .unwrap_or(0);
11138 return Ok(Value::Number(n as f64));
11139 },
11140 #[cfg(not(target_arch = "wasm32"))]
11142 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" =>
11143 {
11144 let id = self.arg_num(&args, 0, 0.0)? as i64;
11145 let mut out = Vec::new();
11146 if let Some(m) = self.midis.get(id as usize) {
11147 for n in &m.notes {
11148 out.push(Value::Number(n.time as f64));
11149 out.push(Value::Number(n.midi as f64));
11150 }
11151 }
11152 return Ok(Value::List(Rc::new(out)));
11153 },
11154 #[cfg(not(target_arch = "wasm32"))]
11156 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" =>
11157 {
11158 let id = self.arg_num(&args, 0, 0.0)? as i64;
11159 let mut out = Vec::new();
11160 if let Some(m) = self.midis.get(id as usize) {
11161 for n in &m.notes {
11162 out.push(Value::Number(n.time as f64));
11163 out.push(Value::Number(n.midi as f64));
11164 out.push(Value::Number(n.dur as f64));
11165 }
11166 }
11167 return Ok(Value::List(Rc::new(out)));
11168 },
11169
11170 #[cfg(not(target_arch = "wasm32"))]
11172 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" =>
11173 {
11174 let id = self.arg_num(&args, 0, 0.0)? as i64;
11175 let nbands = self.arg_num(&args, 1, 16.0)? as usize;
11176 let pos = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
11177 if let Some(t) = self.tracks.get(id as usize) {
11178 let idx = (pos * t.rate as f32) as usize;
11179 let end = (idx + 2048).min(t.mono.len());
11180 if end > idx + 64 {
11181 self.fft.borrow_mut().push_samples(&t.mono[idx..end]);
11182 }
11183 }
11184 let bands = self.fft.borrow().freq_bands(nbands);
11185 return Ok(Value::List(Rc::new(
11186 bands.into_iter().map(|x| Value::Number(x as f64)).collect(),
11187 )));
11188 },
11189
11190 #[cfg(not(target_arch = "wasm32"))]
11192 "audio_stop_sfx" | "停止音效" | "効果音停止" | "효과음정지" | "หยุดเอฟเฟกต์ทั้งหมด" =>
11193 {
11194 if let Some(a) = &self.audio {
11195 a.stop_all_sfx();
11196 }
11197 return Ok(Value::Unit);
11198 },
11199 #[cfg(not(target_arch = "wasm32"))]
11201 "audio_sfx" | "音效" | "空間効果音" | "공간효과음" | "เสียงเอฟเฟกต์" =>
11202 {
11203 let x = self.arg_num(&args, 0, 0.0)? as f32;
11204 let y = self.arg_num(&args, 1, 0.0)? as f32;
11205 let z = self.arg_num(&args, 2, 0.0)? as f32;
11206 let w = self.arg_num(&args, 3, 1.0)? as f32;
11207 let freq = self.arg_num(&args, 4, 440.0)? as f32;
11208 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11209 let dur = self.arg_num(&args, 6, 0.15)? as f32;
11210 let wave = Wave::from_name(&self.arg_str(&args, 7, "sine"));
11211 if let Some(a) = &self.audio {
11212 a.sfx(x, y, z, w, freq, amp, dur, wave);
11213 }
11214 return Ok(Value::Unit);
11215 },
11216 #[cfg(not(target_arch = "wasm32"))]
11221 "morph_note" | "โน้ตมอร์ฟ" | "变形音" | "モーフ音" | "모프음" =>
11222 {
11223 let x = self.arg_num(&args, 0, 0.0)? as f32;
11224 let y = self.arg_num(&args, 1, 0.0)? as f32;
11225 let z = self.arg_num(&args, 2, 0.0)? as f32;
11226 let w = self.arg_num(&args, 3, 1.0)? as f32;
11227 let freq = self.arg_num(&args, 4, 220.0)? as f32;
11228 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11229 let dur = self.arg_num(&args, 6, 0.6)? as f32;
11230 let material = self.arg_num(&args, 7, 0.0)?.clamp(0.0, 3.0) as u8;
11231 let morph = self.arg_num(&args, 8, 0.0)? as f32;
11232 if let Some(a) = &self.audio {
11233 a.morph_note(x, y, z, w, freq, amp, dur, material, morph);
11234 }
11235 return Ok(Value::Unit);
11236 },
11237 #[cfg(not(target_arch = "wasm32"))]
11239 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" =>
11240 {
11241 let path = self.arg_str(&args, 0, "");
11242 let resolved = if std::path::Path::new(&path).exists() {
11243 path.clone()
11244 } else if let Some(d) = &self.source_dir {
11245 d.join(&path).to_string_lossy().into_owned()
11246 } else {
11247 path.clone()
11248 };
11249 match ling_music::load(&resolved) {
11250 Ok(t) => {
11251 if let Some(a) = &self.audio {
11252 return Ok(Value::Number(a.add_sample(t.mono, t.rate) as f64));
11253 }
11254 return Ok(Value::Number(-1.0));
11255 },
11256 Err(e) => {
11257 eprintln!("audio_sample_load failed ({path}): {e}");
11258 return Ok(Value::Number(-1.0));
11259 },
11260 }
11261 },
11262 #[cfg(not(target_arch = "wasm32"))]
11263 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" =>
11264 {
11265 let id = self.arg_num(&args, 0, 0.0)? as usize;
11266 let x = self.arg_num(&args, 1, 0.0)? as f32;
11267 let y = self.arg_num(&args, 2, 0.0)? as f32;
11268 let z = self.arg_num(&args, 3, 0.0)? as f32;
11269 let w = self.arg_num(&args, 4, 1.0)? as f32;
11270 let vol = self.arg_num(&args, 5, 1.0)? as f32;
11271 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
11272 let v = self
11273 .audio
11274 .as_ref()
11275 .map(|a| a.play_sample(id, x, y, z, w, vol, looping))
11276 .unwrap_or(0);
11277 return Ok(Value::Number(v as f64));
11278 },
11279 #[cfg(not(target_arch = "wasm32"))]
11280 "audio_sample_stop" | "停止采样" | "サンプル停止" | "샘플정지" | "หยุดตัวอย่างเสียง" =>
11281 {
11282 let v = self.arg_num(&args, 0, 0.0)? as u32;
11283 if let Some(a) = &self.audio {
11284 a.stop_sample(v);
11285 }
11286 return Ok(Value::Unit);
11287 },
11288 #[cfg(not(target_arch = "wasm32"))]
11290 "audio_fx_delay" | "回声" | "ディレイ効果" | "딜레이" | "เสียงสะท้อน" =>
11291 {
11292 let time = self.arg_num(&args, 0, 0.3)? as f32;
11293 let fb = self.arg_num(&args, 1, 0.3)? as f32;
11294 let mix = self.arg_num(&args, 2, 0.3)? as f32;
11295 if let Some(a) = &self.audio {
11296 a.fx_delay(time, fb, mix);
11297 }
11298 return Ok(Value::Unit);
11299 },
11300 #[cfg(not(target_arch = "wasm32"))]
11301 "audio_fx_reverb" | "混响" | "リバーブ" | "리버브" | "เสียงก้อง" =>
11302 {
11303 let mix = self.arg_num(&args, 0, 0.3)? as f32;
11304 if let Some(a) = &self.audio {
11305 a.fx_reverb(mix);
11306 }
11307 return Ok(Value::Unit);
11308 },
11309 #[cfg(not(target_arch = "wasm32"))]
11310 "audio_fx_lowpass" | "低通滤波" | "ローパス" | "저역통과" | "กรองความถี่ต่ำ" =>
11311 {
11312 let cutoff = self.arg_num(&args, 0, 1.0)? as f32;
11313 if let Some(a) = &self.audio {
11314 a.fx_lowpass(cutoff);
11315 }
11316 return Ok(Value::Unit);
11317 },
11318
11319 #[cfg(not(target_arch = "wasm32"))]
11326 "soft_ball" | "软球" | "ソフトボール" | "소프트볼" | "ลูกบอลนุ่ม" =>
11327 {
11328 let x = self.arg_num(&args, 0, 0.)? as f32;
11329 let y = self.arg_num(&args, 1, 0.)? as f32;
11330 let z = self.arg_num(&args, 2, 0.)? as f32;
11331 let r = self.arg_num(&args, 3, 1.0)? as f32;
11332 let b = ling_physics::soft::SoftBody::sphere(
11333 ling_physics::Vec3::new(x, y, z),
11334 r,
11335 8,
11336 12,
11337 1.0,
11338 );
11339 let id = self.soft_bodies.len();
11340 self.soft_bodies.push(b);
11341 return Ok(Value::Number(id as f64));
11342 },
11343 #[cfg(not(target_arch = "wasm32"))]
11344 "soft_step" | "软体步进" | "ソフト更新" | "소프트스텝" | "ก้าวนุ่ม" =>
11345 {
11346 let id = self.arg_num(&args, 0, 0.)? as usize;
11347 let dt = self.arg_num(&args, 1, 0.016)? as f32;
11348 let gy = self.arg_num(&args, 2, 15.0)? as f32;
11349 if let Some(b) = self.soft_bodies.get_mut(id) {
11350 b.integrate(dt, ling_physics::Vec3::new(0.0, gy, 0.0), 4);
11351 }
11352 return Ok(Value::Unit);
11353 },
11354 #[cfg(not(target_arch = "wasm32"))]
11355 "soft_bounce" | "软体落地" | "ソフト着地" | "소프트바운스" | "เด้งนุ่ม" =>
11356 {
11357 let id = self.arg_num(&args, 0, 0.)? as usize;
11358 let fy = self.arg_num(&args, 1, 0.)? as f32;
11359 let rest = self.arg_num(&args, 2, 0.5)? as f32;
11360 if let Some(b) = self.soft_bodies.get_mut(id) {
11361 b.floor_collision(fy, rest);
11362 }
11363 return Ok(Value::Unit);
11364 },
11365 #[cfg(not(target_arch = "wasm32"))]
11366 "soft_contain" | "软体边界" | "ソフト箱" | "소프트경계" | "กล่องนุ่ม" =>
11367 {
11368 let id = self.arg_num(&args, 0, 0.)? as usize;
11369 let nx = self.arg_num(&args, 1, -5.)? as f32;
11370 let ny = self.arg_num(&args, 2, -5.)? as f32;
11371 let nz = self.arg_num(&args, 3, -5.)? as f32;
11372 let mx = self.arg_num(&args, 4, 5.)? as f32;
11373 let my = self.arg_num(&args, 5, 5.)? as f32;
11374 let mz = self.arg_num(&args, 6, 5.)? as f32;
11375 let rest = self.arg_num(&args, 7, 0.6)? as f32;
11376 if let Some(b) = self.soft_bodies.get_mut(id) {
11377 b.contain(
11378 ling_physics::Vec3::new(nx, ny, nz),
11379 ling_physics::Vec3::new(mx, my, mz),
11380 rest,
11381 );
11382 }
11383 return Ok(Value::Unit);
11384 },
11385 #[cfg(not(target_arch = "wasm32"))]
11386 "soft_kick" | "软体踢" | "ソフト衝撃" | "소프트킥" | "เตะนุ่ม" =>
11387 {
11388 let id = self.arg_num(&args, 0, 0.)? as usize;
11389 let dx = self.arg_num(&args, 1, 0.)? as f32;
11390 let dy = self.arg_num(&args, 2, 0.)? as f32;
11391 let dz = self.arg_num(&args, 3, 0.)? as f32;
11392 let s = self.arg_num(&args, 4, 0.1)? as f32;
11393 if let Some(b) = self.soft_bodies.get_mut(id) {
11394 b.kick(ling_physics::Vec3::new(dx, dy, dz), s);
11395 }
11396 return Ok(Value::Unit);
11397 },
11398 #[cfg(not(target_arch = "wasm32"))]
11401 "soft_spin" | "软体自旋" | "ソフト回転" | "소프트회전" | "หมุนนุ่ม" =>
11402 {
11403 let id = self.arg_num(&args, 0, 0.)? as usize;
11404 let ax = self.arg_num(&args, 1, 0.)? as f32;
11405 let ay = self.arg_num(&args, 2, 0.)? as f32;
11406 let az = self.arg_num(&args, 3, 0.)? as f32;
11407 let rate = self.arg_num(&args, 4, 0.1)? as f32;
11408 if let Some(b) = self.soft_bodies.get_mut(id) {
11409 b.spin(ling_physics::Vec3::new(ax, ay, az), rate);
11410 }
11411 return Ok(Value::Unit);
11412 },
11413 #[cfg(not(target_arch = "wasm32"))]
11414 "soft_deform" | "形变量" | "変形量" | "변형량" | "ความบิดเบี้ยว" =>
11415 {
11416 let id = self.arg_num(&args, 0, 0.)? as usize;
11417 let d = self
11418 .soft_bodies
11419 .get(id)
11420 .map(|b| b.deformation())
11421 .unwrap_or(0.0);
11422 return Ok(Value::Number(d as f64));
11423 },
11424 #[cfg(not(target_arch = "wasm32"))]
11427 "soft_angular_speed"
11428 | "软体角速"
11429 | "ソフト角速度"
11430 | "소프트각속도"
11431 | "ความเร็วเชิงมุมนุ่ม" => {
11432 let id = self.arg_num(&args, 0, 0.)? as usize;
11433 let w = self
11434 .soft_bodies
11435 .get(id)
11436 .map(|b| b.angular_speed())
11437 .unwrap_or(0.0);
11438 return Ok(Value::Number(w as f64));
11439 },
11440 #[cfg(not(target_arch = "wasm32"))]
11441 "soft_centroid" | "软体质心" | "ソフト重心" | "소프트중심" | "จุดศูนย์กลางนุ่ม" =>
11442 {
11443 let id = self.arg_num(&args, 0, 0.)? as usize;
11444 let c = self
11445 .soft_bodies
11446 .get(id)
11447 .map(|b| b.centroid())
11448 .unwrap_or(ling_physics::Vec3::ZERO);
11449 return Ok(Value::List(Rc::new(vec![
11450 Value::Number(c.x as f64),
11451 Value::Number(c.y as f64),
11452 Value::Number(c.z as f64),
11453 ])));
11454 },
11455 #[cfg(not(target_arch = "wasm32"))]
11457 "soft_nodes" | "软体节点" | "ソフト節点" | "소프트노드" | "จุดนุ่ม" =>
11458 {
11459 let id = self.arg_num(&args, 0, 0.)? as usize;
11460 let mut out = Vec::new();
11461 if let Some(b) = self.soft_bodies.get(id) {
11462 for n in &b.nodes {
11463 out.push(Value::Number(n.pos.x as f64));
11464 out.push(Value::Number(n.pos.y as f64));
11465 out.push(Value::Number(n.pos.z as f64));
11466 }
11467 }
11468 return Ok(Value::List(Rc::new(out)));
11469 },
11470
11471 #[cfg(not(target_arch = "wasm32"))]
11473 "rb_add" | "刚体添加" | "剛体追加" | "강체추가" | "เพิ่มวัตถุแข็ง" =>
11474 {
11475 let x = self.arg_num(&args, 0, 0.)? as f32;
11476 let y = self.arg_num(&args, 1, 0.)? as f32;
11477 let z = self.arg_num(&args, 2, 0.)? as f32;
11478 let mass = self.arg_num(&args, 3, 1.0)? as f32;
11479 let mut b =
11480 ling_physics::rigid::RigidBody::new(ling_physics::Vec3::new(x, y, z), mass);
11481 b.restitution = 0.6;
11482 return Ok(Value::Number(self.rigid_world.add(b) as f64));
11483 },
11484 #[cfg(not(target_arch = "wasm32"))]
11485 "rb_torque" | "扭矩" | "トルク" | "토크" | "แรงบิด" => {
11486 let i = self.arg_num(&args, 0, 0.)? as usize;
11487 let tx = self.arg_num(&args, 1, 0.)? as f32;
11488 let ty = self.arg_num(&args, 2, 0.)? as f32;
11489 let tz = self.arg_num(&args, 3, 0.)? as f32;
11490 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11491 b.apply_torque(ling_physics::Vec3::new(tx, ty, tz));
11492 }
11493 return Ok(Value::Unit);
11494 },
11495 #[cfg(not(target_arch = "wasm32"))]
11496 "rb_spin" | "自旋" | "スピン" | "스핀" | "หมุน" => {
11497 let i = self.arg_num(&args, 0, 0.)? as usize;
11498 let wx = self.arg_num(&args, 1, 0.)? as f32;
11499 let wy = self.arg_num(&args, 2, 0.)? as f32;
11500 let wz = self.arg_num(&args, 3, 0.)? as f32;
11501 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11502 b.apply_spin(ling_physics::Vec3::new(wx, wy, wz));
11503 }
11504 return Ok(Value::Unit);
11505 },
11506 #[cfg(not(target_arch = "wasm32"))]
11507 "rb_impulse" | "刚体冲量" | "剛体インパルス" | "강체충격" | "แรงดลแข็ง" =>
11508 {
11509 let i = self.arg_num(&args, 0, 0.)? as usize;
11510 let ix = self.arg_num(&args, 1, 0.)? as f32;
11511 let iy = self.arg_num(&args, 2, 0.)? as f32;
11512 let iz = self.arg_num(&args, 3, 0.)? as f32;
11513 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11514 b.apply_impulse(ling_physics::Vec3::new(ix, iy, iz));
11515 }
11516 return Ok(Value::Unit);
11517 },
11518 #[cfg(not(target_arch = "wasm32"))]
11519 "rb_floor" | "刚体落地" | "剛体着地" | "강체바닥" | "พื้นแข็ง" =>
11520 {
11521 let i = self.arg_num(&args, 0, 0.)? as usize;
11522 let fy = self.arg_num(&args, 1, 0.)? as f32;
11523 let rest = self.arg_num(&args, 2, 0.6)? as f32;
11524 let fric = self.arg_num(&args, 3, 0.6)? as f32;
11525 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11526 b.bounce_floor(fy, rest, fric);
11527 }
11528 return Ok(Value::Unit);
11529 },
11530 #[cfg(not(target_arch = "wasm32"))]
11531 "rb_gravity" | "刚体重力" | "剛体重力" | "강체중력" | "แรงโน้มถ่วงแข็ง" =>
11532 {
11533 let gx = self.arg_num(&args, 0, 0.)? as f32;
11534 let gy = self.arg_num(&args, 1, 9.81)? as f32;
11535 let gz = self.arg_num(&args, 2, 0.)? as f32;
11536 self.rigid_world.gravity = ling_physics::Vec3::new(gx, gy, gz);
11537 return Ok(Value::Unit);
11538 },
11539 #[cfg(not(target_arch = "wasm32"))]
11540 "rb_step" | "刚体步进" | "剛体更新" | "강체스텝" | "ก้าวแข็ง" =>
11541 {
11542 let dt = self.arg_num(&args, 0, 0.016)? as f32;
11543 self.rigid_world.step(dt);
11544 return Ok(Value::Unit);
11545 },
11546 #[cfg(not(target_arch = "wasm32"))]
11547 "rb_pos" | "刚体位置" | "剛体位置" | "강체위치" | "ตำแหน่งแข็ง" =>
11548 {
11549 let i = self.arg_num(&args, 0, 0.)? as usize;
11550 let p = self
11551 .rigid_world
11552 .bodies
11553 .get(i)
11554 .map(|b| b.pos)
11555 .unwrap_or(ling_physics::Vec3::ZERO);
11556 return Ok(Value::List(Rc::new(vec![
11557 Value::Number(p.x as f64),
11558 Value::Number(p.y as f64),
11559 Value::Number(p.z as f64),
11560 ])));
11561 },
11562 #[cfg(not(target_arch = "wasm32"))]
11563 "rb_rot" | "刚体旋转" | "剛体回転" | "강체회전" | "การหมุนแข็ง" =>
11564 {
11565 let i = self.arg_num(&args, 0, 0.)? as usize;
11566 let q = self
11567 .rigid_world
11568 .bodies
11569 .get(i)
11570 .map(|b| b.orientation)
11571 .unwrap_or(ling_physics::Quat::IDENTITY);
11572 return Ok(Value::List(Rc::new(vec![
11573 Value::Number(q.x as f64),
11574 Value::Number(q.y as f64),
11575 Value::Number(q.z as f64),
11576 Value::Number(q.w as f64),
11577 ])));
11578 },
11579
11580 #[cfg(not(target_arch = "wasm32"))]
11582 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" =>
11583 {
11584 let path = self.arg_str(&args, 0, "");
11585 let resolved = if std::path::Path::new(&path).exists() {
11586 path.clone()
11587 } else if let Some(d) = &self.source_dir {
11588 d.join(&path).to_string_lossy().into_owned()
11589 } else {
11590 path.clone()
11591 };
11592 let bytes = match std::fs::read(&resolved) {
11593 Ok(b) => b,
11594 Err(e) => {
11595 eprintln!("mesh_load failed ({path}): {e}");
11596 return Ok(Value::Number(-1.0));
11597 },
11598 };
11599 if bytes.len() < 16 || &bytes[0..4] != b"LMSH" {
11600 eprintln!("mesh_load: bad header ({path})");
11601 return Ok(Value::Number(-1.0));
11602 }
11603 let rd4 =
11604 |o: usize| -> [u8; 4] { [bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]] };
11605 let height = f32::from_le_bytes(rd4(8));
11606 let ntri = u32::from_le_bytes(rd4(12)) as usize;
11607 let need = 16usize.saturating_add(ntri.saturating_mul(9 * 4 + 3));
11608 if bytes.len() < need {
11609 eprintln!("mesh_load: truncated ({path})");
11610 return Ok(Value::Number(-1.0));
11611 }
11612 let mut pos = Vec::with_capacity(ntri * 3);
11613 let mut col = Vec::with_capacity(ntri);
11614 let mut off = 16usize;
11615 for _ in 0..ntri {
11616 for _k in 0..3 {
11617 let x = f32::from_le_bytes(rd4(off));
11618 let y = f32::from_le_bytes(rd4(off + 4));
11619 let z = f32::from_le_bytes(rd4(off + 8));
11620 off += 12;
11621 pos.push([x, y, z]);
11622 }
11623 col.push([bytes[off], bytes[off + 1], bytes[off + 2]]);
11624 off += 3;
11625 }
11626 eprintln!("mesh_load: {} ({} tris, h={:.2})", path, ntri, height);
11627 let id = self.meshes.len();
11628 self.meshes
11629 .push(crate::gfx::shapes::ColorMesh { pos, col, height });
11630 return Ok(Value::Number(id as f64));
11631 },
11632 #[cfg(target_arch = "wasm32")]
11633 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" =>
11634 {
11635 return Ok(Value::Number(-1.0));
11638 },
11639 #[cfg(not(target_arch = "wasm32"))]
11640 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" =>
11641 {
11642 let id = self.arg_num(&args, 0, 0.)? as usize;
11644 let cx = self.arg_num(&args, 1, 0.)? as f32;
11645 let cy = self.arg_num(&args, 2, 0.)? as f32;
11646 let cz = self.arg_num(&args, 3, 0.)? as f32;
11647 let sc = self.arg_num(&args, 4, 1.)? as f32;
11648 let yaw = self.arg_num(&args, 5, 0.)? as f32;
11649 let sway = self.arg_num(&args, 6, 0.)? as f32;
11650 let arm = self.arg_num(&args, 7, 0.)? as f32;
11651 let lean = self.arg_num(&args, 8, 0.)? as f32;
11652 let leg = self.arg_num(&args, 9, 0.)? as f32;
11653 let tuck = self.arg_num(&args, 10, 0.)? as f32;
11654 if id < self.meshes.len() {
11655 let m = &self.meshes[id];
11656 let mut gfx = self.gfx.borrow_mut();
11657 gfx.draw_color_mesh(m, cx, cy, cz, sc, yaw, sway, arm, lean, leg, tuck);
11658 }
11659 return Ok(Value::Unit);
11660 },
11661 #[cfg(target_arch = "wasm32")]
11662 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" =>
11663 {
11664 return Ok(Value::Unit);
11665 },
11666
11667 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" =>
11669 {
11670 let w = self.arg_num(&args, 0, 64.)? as usize;
11671 let h = self.arg_num(&args, 1, 64.)? as usize;
11672 let id = self.liquids.len();
11673 self.liquids
11674 .push(ling_physics::liquid::LiquidGrid::new(w, h));
11675 return Ok(Value::Number(id as f64));
11676 },
11677 "liquid_set_colors" | "液体颜色" | "液体配色" | "액체색상" | "สีของเหลว" =>
11678 {
11679 let id = self.arg_num(&args, 0, 0.)? as usize;
11680 let wr = self.arg_num(&args, 1, 40.)? as f32;
11681 let wg = self.arg_num(&args, 2, 110.)? as f32;
11682 let wb = self.arg_num(&args, 3, 235.)? as f32;
11683 let or_ = self.arg_num(&args, 4, 240.)? as f32;
11684 let og = self.arg_num(&args, 5, 175.)? as f32;
11685 let ob = self.arg_num(&args, 6, 45.)? as f32;
11686 if let Some(g) = self.liquids.get_mut(id) {
11687 g.set_colors(wr, wg, wb, or_, og, ob);
11688 }
11689 return Ok(Value::Unit);
11690 },
11691 "liquid_splat" | "液体注入" | "液体追加" | "액체분사" | "หยดของเหลว" =>
11692 {
11693 let id = self.arg_num(&args, 0, 0.)? as usize;
11694 let x = self.arg_num(&args, 1, 0.)? as f32;
11695 let y = self.arg_num(&args, 2, 0.)? as f32;
11696 let kind = self.arg_num(&args, 3, 0.)? as i32;
11697 let amt = self.arg_num(&args, 4, 1.0)? as f32;
11698 let rad = self.arg_num(&args, 5, 4.0)? as f32;
11699 if let Some(g) = self.liquids.get_mut(id) {
11700 g.splat(x, y, kind, amt, rad);
11701 }
11702 return Ok(Value::Unit);
11703 },
11704 "liquid_gravity" | "液体重力" | "液体重力ベクトル" | "액체중력" | "แรงโน้มถ่วงเหลว" =>
11705 {
11706 let id = self.arg_num(&args, 0, 0.)? as usize;
11707 let gx = self.arg_num(&args, 1, 0.)? as f32;
11708 let gy = self.arg_num(&args, 2, 60.)? as f32;
11709 if let Some(g) = self.liquids.get_mut(id) {
11710 g.set_gravity(gx, gy);
11711 }
11712 return Ok(Value::Unit);
11713 },
11714 "liquid_step" | "液体步进" | "液体更新" | "액체스텝" | "ก้าวของเหลว" =>
11715 {
11716 let id = self.arg_num(&args, 0, 0.)? as usize;
11717 let dt = self.arg_num(&args, 1, 0.016)? as f32;
11718 if let Some(g) = self.liquids.get_mut(id) {
11719 g.step(dt);
11720 }
11721 return Ok(Value::Unit);
11722 },
11723 "liquid_step_all"
11729 | "液体全步进"
11730 | "液体全更新"
11731 | "전체액체스텝"
11732 | "ก้าวของเหลวทั้งหมด" => {
11733 let dt = self.arg_num(&args, 0, 0.016)? as f32;
11734 ling_physics::liquid::step_all(&mut self.liquids, dt);
11735 return Ok(Value::Unit);
11736 },
11737 "liquid_rainbow" | "液体彩虹" | "液体虹" | "액체무지개" | "ของเหลวสายรุ้ง" =>
11739 {
11740 let id = self.arg_num(&args, 0, 0.)? as usize;
11741 let on = self.arg_num(&args, 1, 1.0)? > 0.5;
11742 if let Some(g) = self.liquids.get_mut(id) {
11743 g.rainbow = on;
11744 }
11745 return Ok(Value::Unit);
11746 },
11747 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" =>
11749 {
11750 let id = self.arg_num(&args, 0, 0.)? as usize;
11751 let m = self.liquids.get(id).map(|g| g.mix_amount()).unwrap_or(0.0);
11752 return Ok(Value::Number(m as f64));
11753 },
11754 #[cfg(not(target_arch = "wasm32"))]
11756 "liquid_draw" | "绘制液体" | "液体描画" | "액체그리기" | "วาดของเหลว" =>
11757 {
11758 let id = self.arg_num(&args, 0, 0.)? as usize;
11759 let sx = self.arg_num(&args, 1, 0.)? as i32;
11760 let sy = self.arg_num(&args, 2, 0.)? as i32;
11761 let scale = (self.arg_num(&args, 3, 4.)? as i32).max(1);
11762 if id < self.liquids.len() {
11763 let (gw, gh) = {
11764 let g = &self.liquids[id];
11765 (g.w, g.h)
11766 };
11767 let mut gfx = self.gfx.borrow_mut();
11768 let (w, h) = (gfx.width as i32, gfx.height as i32);
11769 let g = &self.liquids[id];
11770 for cy in 0..gh {
11771 for cx in 0..gw {
11772 let col = g.sample_rgb(cx, cy);
11773 let bx = sx + cx as i32 * scale;
11774 let by = sy + cy as i32 * scale;
11775 for dy in 0..scale {
11776 for dx in 0..scale {
11777 let px = bx + dx;
11778 let py = by + dy;
11779 if px >= 0 && py >= 0 && px < w && py < h {
11780 gfx.buffer[(py * w + px) as usize] = col;
11781 }
11782 }
11783 }
11784 }
11785 }
11786 }
11787 return Ok(Value::Unit);
11788 },
11789 "liquid_draw_surface" | "液体贴面" | "液体曲面" | "액체곡면" | "ของเหลวบนพื้นผิว" =>
11792 {
11793 #[cfg(not(target_arch = "wasm32"))]
11794 {
11795 let id = self.arg_num(&args, 0, 0.)? as usize;
11796 let kind = self.arg_num(&args, 1, 1.)? as i32;
11797 let cx = self.arg_num(&args, 2, 0.)? as f32;
11798 let cy = self.arg_num(&args, 3, 0.)? as f32;
11799 let cz = self.arg_num(&args, 4, 0.)? as f32;
11800 let radius = self.arg_num(&args, 5, 2.0)? as f32;
11801 let height = self.arg_num(&args, 6, 3.0)? as f32;
11802 if id < self.liquids.len() {
11803 let (gw, gh) = {
11804 let g = &self.liquids[id];
11805 (g.w, g.h)
11806 };
11807 let mut gfx = self.gfx.borrow_mut();
11808 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
11809 let cam = gfx.camera.clone();
11810 let near = -cam.zdist + 0.05;
11811 let g = &self.liquids[id];
11812 let tau = std::f32::consts::TAU;
11813 let pi = std::f32::consts::PI;
11814 let sp = |u: f32, v: f32| -> [f32; 3] {
11816 if kind == 0 {
11817 [
11818 cx + (u - 0.5) * 2.0 * radius,
11819 cy,
11820 cz + (v - 0.5) * 2.0 * radius,
11821 ]
11822 } else if kind == 2 {
11823 let th = u * tau;
11824 [
11825 cx + th.cos() * radius,
11826 cy + (v - 0.5) * height,
11827 cz + th.sin() * radius,
11828 ]
11829 } else if kind == 3 {
11830 let th = u * tau;
11831 let rr = radius * (1.0 - v);
11832 [
11833 cx + th.cos() * rr,
11834 cy + (v - 0.5) * height,
11835 cz + th.sin() * rr,
11836 ]
11837 } else if kind == 4 {
11838 let th = u * tau;
11839 let ph = v * pi * 0.5;
11840 [
11841 cx + ph.sin() * th.cos() * radius,
11842 cy - ph.cos() * radius,
11843 cz + ph.sin() * th.sin() * radius,
11844 ]
11845 } else {
11846 let th = u * tau;
11847 let ph = v * pi;
11848 [
11849 cx + ph.sin() * th.cos() * radius,
11850 cy + ph.cos() * radius,
11851 cz + ph.sin() * th.sin() * radius,
11852 ]
11853 }
11854 };
11855 let nrm = |u: f32, v: f32| -> [f32; 3] {
11856 if kind == 0 {
11857 [0.0, -1.0, 0.0]
11858 } else if kind == 2 {
11859 let th = u * tau;
11860 [th.cos(), 0.0, th.sin()]
11861 } else if kind == 3 {
11862 let th = u * tau;
11863 let s = (radius / height.max(0.01)).atan();
11864 [th.cos() * s.cos(), s.sin(), th.sin() * s.cos()]
11865 } else if kind == 4 {
11866 let th = u * tau;
11867 let ph = v * pi * 0.5;
11868 [ph.sin() * th.cos(), -ph.cos(), ph.sin() * th.sin()]
11869 } else {
11870 let th = u * tau;
11871 let ph = v * pi;
11872 [ph.sin() * th.cos(), ph.cos(), ph.sin() * th.sin()]
11873 }
11874 };
11875 let gwf = gw as f32;
11876 let ghf = gh as f32;
11877 let mut cyc = 0usize;
11878 while cyc < gh {
11879 let mut cxc = 0usize;
11880 while cxc < gw {
11881 let uc = (cxc as f32 + 0.5) / gwf;
11883 let vc = (cyc as f32 + 0.5) / ghf;
11884 let c = sp(uc, vc);
11885 let n = nrm(uc, vc);
11886 let dc = cam.depth(c[0], c[1], c[2]);
11887 if dc > near {
11888 let cull = kind != 0
11889 && cam.depth(
11890 c[0] + n[0] * 0.06,
11891 c[1] + n[1] * 0.06,
11892 c[2] + n[2] * 0.06,
11893 ) > dc;
11894 if !cull {
11895 let u0 = cxc as f32 / gwf;
11897 let u1 = (cxc + 1) as f32 / gwf;
11898 let v0 = cyc as f32 / ghf;
11899 let v1 = (cyc + 1) as f32 / ghf;
11900 let q = [sp(u0, v0), sp(u1, v0), sp(u1, v1), sp(u0, v1)];
11901 let mut poly: Vec<[f32; 2]> = Vec::with_capacity(5);
11902 let mut ok = true;
11903 for p in &q {
11904 if cam.depth(p[0], p[1], p[2]) <= near {
11905 ok = false;
11906 break;
11907 }
11908 let (sx, sy, _) = cam.project(p[0], p[1], p[2]);
11909 poly.push([sx, sy]);
11910 }
11911 if ok {
11912 let p0 = poly[0];
11913 poly.push(p0);
11914 let col = g.sample_rgb(cxc, cyc);
11915 crate::gfx::raster::fill_contours_aa(
11916 &mut gfx.buffer,
11917 w,
11918 h,
11919 col,
11920 add,
11921 std::slice::from_ref(&poly),
11922 );
11923 }
11924 }
11925 }
11926 cxc += 1;
11927 }
11928 cyc += 1;
11929 }
11930 }
11931 }
11932 #[cfg(target_arch = "wasm32")]
11933 {
11934 }
11937 return Ok(Value::Unit);
11938 },
11939 #[cfg(not(target_arch = "wasm32"))]
11942 "sparkle" | "闪光" | "きらめき" | "반짝임" | "ประกาย" => {
11943 let x = self.arg_num(&args, 0, 0.)? as f32;
11944 let y = self.arg_num(&args, 1, 0.)? as f32;
11945 let ww = self.arg_num(&args, 2, 200.)? as f32;
11946 let hh = self.arg_num(&args, 3, 200.)? as f32;
11947 let count = self.arg_num(&args, 4, 40.)? as i32;
11948 let t = self.arg_num(&args, 5, 0.)? as f32;
11949 let mut gfx = self.gfx.borrow_mut();
11950 let (w, h, add, color) = (gfx.width, gfx.height, gfx.blend == 1, gfx.color);
11951 let (cr, cg, cb) = (
11952 (color >> 16 & 0xFF) as f32,
11953 (color >> 8 & 0xFF) as f32,
11954 (color & 0xFF) as f32,
11955 );
11956 let mut n = 0i32;
11957 while n < count {
11958 let hsh = (n as u32).wrapping_mul(2654435761).wrapping_add(0x9E3779B9);
11959 let u = ((hsh >> 8) & 1023) as f32 / 1023.0;
11960 let v = ((hsh >> 18) & 1023) as f32 / 1023.0;
11961 let phase = (hsh & 255) as f32 / 255.0;
11962 let tw = (t * 3.0 + phase * std::f32::consts::TAU + n as f32).sin() * 0.5 + 0.5;
11963 let sz = 1.5 + tw * 5.0;
11964 let px = x + u * ww;
11965 let py = y + v * hh;
11966 let b = tw * tw; let col =
11968 (((cr * b) as u32) << 16) | (((cg * b) as u32) << 8) | ((cb * b) as u32);
11969 crate::gfx::raster::draw_line_aa(
11970 &mut gfx.buffer,
11971 w,
11972 h,
11973 col,
11974 add,
11975 px - sz,
11976 py,
11977 px + sz,
11978 py,
11979 );
11980 crate::gfx::raster::draw_line_aa(
11981 &mut gfx.buffer,
11982 w,
11983 h,
11984 col,
11985 add,
11986 px,
11987 py - sz,
11988 px,
11989 py + sz,
11990 );
11991 let d = sz * 0.55;
11992 crate::gfx::raster::draw_line_aa(
11993 &mut gfx.buffer,
11994 w,
11995 h,
11996 col,
11997 add,
11998 px - d,
11999 py - d,
12000 px + d,
12001 py + d,
12002 );
12003 crate::gfx::raster::draw_line_aa(
12004 &mut gfx.buffer,
12005 w,
12006 h,
12007 col,
12008 add,
12009 px - d,
12010 py + d,
12011 px + d,
12012 py - d,
12013 );
12014 n += 1;
12015 }
12016 return Ok(Value::Unit);
12017 },
12018
12019 #[cfg(not(target_arch = "wasm32"))]
12025 "dialog_show" | "对话显示" | "会話表示" | "대화표시" | "แสดงบทสนทนา" =>
12026 {
12027 let text = self.arg_str(&args, 0, "");
12028 let cps = self.arg_num(&args, 1, 32.0)? as f32;
12029 self.dialog = Some(ling_game::dialog::Dialog::new(&text, cps));
12030 return Ok(Value::Unit);
12031 },
12032 #[cfg(not(target_arch = "wasm32"))]
12033 "dialog_step" | "对话步进" | "会話更新" | "대화스텝" | "ก้าวบทสนทนา" =>
12034 {
12035 let dt = self.arg_num(&args, 0, 0.016)? as f32;
12036 if let Some(d) = self.dialog.as_mut() {
12037 d.update(dt);
12038 }
12039 return Ok(Value::Unit);
12040 },
12041 #[cfg(not(target_arch = "wasm32"))]
12042 "dialog_advance" | "对话推进" | "会話送り" | "대화진행" | "เลื่อนบทสนทนา" =>
12043 {
12044 if let Some(d) = self.dialog.as_mut() {
12045 d.advance();
12046 }
12047 return Ok(Value::Unit);
12048 },
12049 #[cfg(not(target_arch = "wasm32"))]
12050 "dialog_active" | "对话激活" | "会話中" | "대화중" | "บทสนทนาทำงาน" =>
12051 {
12052 let a = self
12053 .dialog
12054 .as_ref()
12055 .map(|d| !d.is_closed())
12056 .unwrap_or(false);
12057 return Ok(Value::Bool(a));
12058 },
12059 #[cfg(not(target_arch = "wasm32"))]
12060 "dialog_typing" | "对话打字" | "会話タイプ中" | "대화타이핑" | "กำลังพิมพ์บทสนทนา" =>
12061 {
12062 use ling_game::dialog::Dialog;
12063
12064 let a = self
12065 .dialog
12066 .as_ref()
12067 .map(|d: &Dialog| !d.is_closed() && d.is_typing())
12068 .unwrap_or(false);
12069 return Ok(Value::Bool(a));
12070 },
12071 #[cfg(not(target_arch = "wasm32"))]
12072 "dialog_close" | "对话关闭" | "会話閉じる" | "대화닫기" | "ปิดบทสนทนา" =>
12073 {
12074 self.dialog = None;
12075 return Ok(Value::Unit);
12076 },
12077 #[cfg(not(target_arch = "wasm32"))]
12079 "dialog_color" | "对话颜色" | "会話色" | "대화색" | "สีบทสนทนา" =>
12080 {
12081 let role = (self.arg_num(&args, 0, 0.0)? as usize).min(3);
12082 let r = self.arg_num(&args, 1, 255.0)? as u32 & 0xFF;
12083 let g = self.arg_num(&args, 2, 255.0)? as u32 & 0xFF;
12084 let b = self.arg_num(&args, 3, 255.0)? as u32 & 0xFF;
12085 self.dialog_colors[role] = (r << 16) | (g << 8) | b;
12086 return Ok(Value::Unit);
12087 },
12088 #[cfg(not(target_arch = "wasm32"))]
12090 "dialog_draw" | "对话绘制" | "会話描画" | "대화그리기" | "วาดบทสนทนา" =>
12091 {
12092 let x = self.arg_num(&args, 0, 40.0)? as f32;
12093 let y = self.arg_num(&args, 1, 0.0)? as f32;
12094 let ww = self.arg_num(&args, 2, 720.0)? as f32;
12095 let hh = self.arg_num(&args, 3, 150.0)? as f32;
12096 let font = self.arg_num(&args, 4, -1.0)? as i64;
12097 let t = (crate::runtime::now_secs() - self.start_time_secs) as f32;
12098 self.render_dialog(x, y, ww, hh, font, t);
12099 return Ok(Value::Unit);
12100 },
12101
12102 #[cfg(not(target_arch = "wasm32"))]
12107 "text_poll" => {
12108 const TEXT_BUFFER_MAX: usize = 240;
12109 #[cfg(windows)]
12118 {
12119 let topmost = self.gfx.borrow().topmost_window;
12120 if topmost {
12121 if !window_is_foreground(self.gfx.borrow().hwnd) {
12122 return Ok(Value::Str(self.text_buffer.clone()));
12123 }
12124 let shift = os_key_down(VK_SHIFT);
12125 let now = crate::runtime::now_secs();
12126 let mut gfx = self.gfx.borrow_mut();
12127 let back_idx = VK_BACK as usize;
12128 let back_down = os_key_down(VK_BACK);
12129 let back_was = gfx.raw_keys_prev[back_idx];
12130 let (mut back_since, mut back_fire) = (
12131 gfx.raw_keys_down_since[back_idx],
12132 gfx.raw_keys_last_fire[back_idx],
12133 );
12134 if key_repeat_fire(now, back_down, back_was, &mut back_since, &mut back_fire) {
12135 self.text_buffer.pop();
12136 }
12137 gfx.raw_keys_down_since[back_idx] = back_since;
12138 gfx.raw_keys_last_fire[back_idx] = back_fire;
12139 gfx.raw_keys_prev[back_idx] = back_down;
12140 for &vk in TEXT_POLL_VKS {
12141 let idx = (vk as usize) & 0xFF;
12142 let down = os_key_down(vk);
12143 let was = gfx.raw_keys_prev[idx];
12144 let (mut since, mut fire) =
12145 (gfx.raw_keys_down_since[idx], gfx.raw_keys_last_fire[idx]);
12146 if key_repeat_fire(now, down, was, &mut since, &mut fire) {
12147 if let Some(c) = vk_char(vk, shift) {
12148 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12149 self.text_buffer.push(c);
12150 }
12151 }
12152 }
12153 gfx.raw_keys_down_since[idx] = since;
12154 gfx.raw_keys_last_fire[idx] = fire;
12155 gfx.raw_keys_prev[idx] = down;
12156 }
12157 return Ok(Value::Str(self.text_buffer.clone()));
12158 }
12159 }
12160 let (keys, shift) = {
12161 let gfx = self.gfx.borrow();
12162 match gfx.window.as_ref() {
12163 Some(w) => (
12164 w.get_keys_pressed(minifb::KeyRepeat::Yes),
12165 w.is_key_down(minifb::Key::LeftShift)
12166 || w.is_key_down(minifb::Key::RightShift),
12167 ),
12168 None => (Vec::new(), false),
12169 }
12170 };
12171 for k in keys {
12172 if k == minifb::Key::Backspace {
12173 self.text_buffer.pop();
12174 } else if let Some(c) = key_char(k, shift) {
12175 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12176 self.text_buffer.push(c);
12177 }
12178 }
12179 }
12180 return Ok(Value::Str(self.text_buffer.clone()));
12181 },
12182 #[cfg(target_arch = "wasm32")]
12183 "text_poll" => {
12184 return Ok(Value::Str(self.text_buffer.clone()));
12185 },
12186 "text_get" => return Ok(Value::Str(self.text_buffer.clone())),
12187 "text_set" => {
12188 self.text_buffer = self.arg_str(&args, 0, "");
12189 return Ok(Value::Unit);
12190 },
12191 "text_clear" => {
12192 self.text_buffer.clear();
12193 return Ok(Value::Unit);
12194 },
12195 #[cfg(not(target_arch = "wasm32"))]
12197 "record_frame" => {
12198 let n = self.record_n;
12199 let (buf, w, h) = {
12200 let gfx = self.gfx.borrow();
12201 (gfx.buffer.clone(), gfx.width, gfx.height)
12202 };
12203 let _ = std::fs::create_dir_all("recordings");
12204 let mut out = Vec::with_capacity(w * h * 3 + 32);
12205 out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
12206 for px in &buf {
12207 let p = *px;
12208 out.push((p >> 16) as u8);
12209 out.push((p >> 8) as u8);
12210 out.push(p as u8);
12211 }
12212 let _ = std::fs::write(format!("recordings/frame_{n:05}.ppm"), out);
12213 self.record_n += 1;
12214 return Ok(Value::Number(n as f64));
12215 },
12216 "record_count" => return Ok(Value::Number(self.record_n as f64)),
12217 #[cfg(not(target_arch = "wasm32"))]
12219 "screenshot" | "บันทึกภาพ" => {
12220 let mode = self.arg_str(&args, 0, "game");
12221 let (buf, w, h) = {
12222 let gfx = self.gfx.borrow();
12223 (gfx.buffer.clone(), gfx.width, gfx.height)
12224 };
12225 let _ = std::fs::create_dir_all("screenshots");
12226 let ts = std::time::SystemTime::now()
12227 .duration_since(std::time::UNIX_EPOCH)
12228 .map(|d| d.as_secs())
12229 .unwrap_or(0);
12230 let safe: String = mode
12231 .chars()
12232 .map(|c| if c.is_alphanumeric() { c } else { '_' })
12233 .collect();
12234 let path = format!("screenshots/ss_{ts}_{safe}_{w}x{h}.png");
12235 let mut rgb = Vec::with_capacity(w * h * 3);
12236 for px in &buf {
12237 let p = *px;
12238 rgb.push((p >> 16) as u8);
12239 rgb.push((p >> 8) as u8);
12240 rgb.push(p as u8);
12241 }
12242 if let Some(img) = image::RgbImage::from_raw(w as u32, h as u32, rgb) {
12243 let _ = img.save(&path);
12244 }
12245 return Ok(Value::Str(path));
12246 },
12247 #[cfg(not(target_arch = "wasm32"))]
12251 "mic_capture" => {
12252 if let Some(mic) = self.mic.as_ref() {
12253 let s = mic.latest_samples();
12254 self.mic_buffer.extend_from_slice(&s);
12255 let cap = 96_000usize; if self.mic_buffer.len() > cap {
12257 let drop = self.mic_buffer.len() - cap;
12258 self.mic_buffer.drain(0..drop);
12259 }
12260 }
12261 return Ok(Value::Number(self.mic_buffer.len() as f64));
12262 },
12263 #[cfg(not(target_arch = "wasm32"))]
12265 "mic_seed" => {
12266 let mut bytes = Vec::with_capacity(self.mic_buffer.len() * 4);
12267 for f in &self.mic_buffer {
12268 bytes.extend_from_slice(&f.to_le_bytes());
12269 }
12270 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(&bytes))));
12271 },
12272 #[cfg(not(target_arch = "wasm32"))]
12273 "mic_clear" => {
12274 self.mic_buffer.clear();
12275 return Ok(Value::Number(0.0));
12276 },
12277 #[cfg(not(target_arch = "wasm32"))]
12280 "flush_3d" | "render_3d" => {
12281 let mut gfx = self.gfx.borrow_mut();
12282 if !gfx.depth_queue.is_empty() {
12283 let w = gfx.width;
12284 let h = gfx.height;
12285 let dt = gfx.depth_test;
12286 let reset_z = gfx.zbuf_needs_clear;
12287 let (bm, ba) = (gfx.blend, gfx.alpha);
12288 let aa = gfx.antialias;
12289 let queue = std::mem::take(&mut gfx.depth_queue);
12290 {
12291 let g = &mut *gfx;
12292 let z = if dt { Some(&mut g.depth_buf) } else { None };
12293 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12294 }
12295 gfx.zbuf_needs_clear = false;
12296 gfx.depth_queue.set_state(bm, ba); }
12298 return Ok(Value::Unit);
12299 },
12300 #[cfg(target_arch = "wasm32")]
12301 "flush_3d" | "render_3d" => {
12302 let mut gfx = self.gfx.borrow_mut();
12303 if !gfx.depth_queue.is_empty() {
12304 let w = gfx.width;
12305 let h = gfx.height;
12306 let dt = gfx.depth_test;
12307 let reset_z = gfx.zbuf_needs_clear;
12308 let (bm, ba) = (gfx.blend, gfx.alpha);
12309 let aa = gfx.antialias;
12310 let queue = std::mem::take(&mut gfx.depth_queue);
12311 {
12312 let g = &mut *gfx;
12313 let z = if dt { Some(&mut g.depth_buf) } else { None };
12314 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12315 }
12316 gfx.zbuf_needs_clear = false;
12317 gfx.depth_queue.set_state(bm, ba);
12318 }
12319 return Ok(Value::Unit);
12320 },
12321
12322 "flush_post" | "post_now" | "포스트플러시" | "后期冲刷" => {
12327 let mut gfx = self.gfx.borrow_mut();
12328 if !gfx.depth_queue.is_empty() {
12329 let w = gfx.width;
12330 let h = gfx.height;
12331 let dt = gfx.depth_test;
12332 let reset_z = gfx.zbuf_needs_clear;
12333 let (bm, ba) = (gfx.blend, gfx.alpha);
12334 let aa = gfx.antialias;
12335 let queue = std::mem::take(&mut gfx.depth_queue);
12336 {
12337 let g = &mut *gfx;
12338 let z = if dt { Some(&mut g.depth_buf) } else { None };
12339 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12340 }
12341 gfx.zbuf_needs_clear = false;
12342 gfx.depth_queue.set_state(bm, ba);
12343 }
12344 gfx.toon_post_process();
12345 gfx.post_done = true;
12346 return Ok(Value::Unit);
12347 },
12348
12349 #[cfg(not(target_arch = "wasm32"))]
12352 "screen_distort" | "บิดจอ" | "屏幕扭曲" | "画面歪み" | "화면왜곡" =>
12353 {
12354 let amount = self.arg_num(&args, 0, 8.0)? as f32;
12355 let t = self.arg_num(&args, 1, 0.0)? as f32;
12356 let step = self.arg_num(&args, 2, 1.0)?.max(1.0) as usize;
12359 let _d = std::time::Instant::now();
12360 self.gfx.borrow_mut().distort(amount, t, step);
12361 ling_phase_add(phase::DISTORT, _d.elapsed().as_nanos());
12362 return Ok(Value::Unit);
12363 },
12364
12365 "set_rim" | "设置边缘光" | "リム設定" | "림라이트" | "ตั้งขอบเรือง" =>
12366 {
12367 let s = self.arg_num(&args, 0, 0.6)? as f32;
12368 let r = self.arg_num(&args, 1, 115.)? as f32 / 255.0;
12369 let g = self.arg_num(&args, 2, 217.)? as f32 / 255.0;
12370 let b = self.arg_num(&args, 3, 255.)? as f32 / 255.0;
12371 let mut gfx = self.gfx.borrow_mut();
12372 gfx.shade.rim = s;
12373 gfx.shade.rim_color = [r, g, b];
12374 return Ok(Value::Unit);
12375 },
12376
12377 n if crate::gfx::shapes::canon(n).is_some() => {
12386 let kind = crate::gfx::shapes::canon(n).unwrap();
12387 let cx = self.arg_num(&args, 0, 0.)? as f32;
12388 let cy = self.arg_num(&args, 1, 0.)? as f32;
12389 let cz = self.arg_num(&args, 2, 0.)? as f32;
12390 let sx = self.arg_num(&args, 3, 1.)? as f32;
12391 let sy = self.arg_num(&args, 4, 1.)? as f32;
12392 let sz = self.arg_num(&args, 5, 1.)? as f32;
12393 let rx = self.arg_num(&args, 6, 0.)? as f32;
12394 let ry = self.arg_num(&args, 7, 0.)? as f32;
12395 let rz = self.arg_num(&args, 8, 0.)? as f32;
12396 let mode = self.arg_num(&args, 9, 0.)? as i32;
12397 let e0 = self.arg_num(&args, 10, 0.)? as f32;
12398 let e1 = self.arg_num(&args, 11, 0.)? as f32;
12399 let e2 = self.arg_num(&args, 12, 0.)? as f32;
12400 if let Some(mesh) = crate::gfx::shapes::build(
12401 kind,
12402 [cx, cy, cz, sx, sy, sz, rx, ry, rz],
12403 e0,
12404 e1,
12405 e2,
12406 ) {
12407 let mut gfx = self.gfx.borrow_mut();
12408 gfx.emit_mesh(&mesh, mode);
12409 }
12410 return Ok(Value::Unit);
12411 },
12412
12413 _ => {},
12414 }
12415
12416 if let Some(field_names) = self.structs.get(name).cloned() {
12418 if args.len() != field_names.len() {
12419 return Err(EvalErr::from(format!(
12420 "{name} expects {} field(s), got {}",
12421 field_names.len(),
12422 args.len()
12423 )));
12424 }
12425 let fields = field_names.into_iter().zip(args).collect();
12426 return Ok(Value::Struct { name: name.to_string(), fields });
12427 }
12428
12429 if let Some((enum_name, arity)) = self.enum_variants.get(name).cloned() {
12431 if args.len() != arity {
12432 return Err(EvalErr::from(format!(
12433 "{name} expects {arity} value(s), got {}",
12434 args.len()
12435 )));
12436 }
12437 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
12438 return Ok(Value::Variant { enum_name, variant, payload: args });
12439 }
12440
12441 #[cfg(target_arch = "wasm32")]
12442 if let Some(v) = wasm_unsupported_builtin(name) {
12443 return Ok(v);
12444 }
12445
12446 Err(EvalErr::from(format!("unknown function '{name}'")))
12447 }
12448
12449 fn call_value(&mut self, v: Value, args: Vec<Value>) -> EvalResult {
12450 match v {
12451 Value::Fn(params, body, mut captured) => {
12452 for (p, a) in params.iter().zip(args) {
12453 captured.insert(p.clone(), a);
12454 }
12455 match self.framed("<closure>", |me| me.exec_block(&body, &mut captured)) {
12456 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
12457 Err(EvalErr::Return(v)) => Ok(v),
12458 Err(e) => Err(e),
12459 }
12460 },
12461 other => Err(EvalErr::from(format!("cannot call {:?}", other))),
12462 }
12463 }
12464
12465 fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
12466 match (&recv, method) {
12467 (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
12468 (Value::Str(s), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(s.len() as f64)),
12475 (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
12476 (Value::Str(s), "contains" | "包含") => {
12477 if let Some(Value::Str(sub)) = args.first() {
12478 Ok(Value::Bool(s.contains(sub.as_str())))
12479 } else {
12480 Ok(Value::Bool(false))
12481 }
12482 },
12483 (Value::Str(s), "push_str" | "推_文") => {
12484 let mut s2 = s.clone();
12485 if let Some(Value::Str(a)) = args.first() {
12486 s2.push_str(a);
12487 }
12488 Ok(Value::Str(s2))
12489 },
12490 (Value::List(v), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(v.len() as f64)),
12491 (Value::List(v), "push" | "推" | "添加" | "追加" | "추가" | "เพิ่ม") => {
12492 let mut v2: Vec<Value> = (**v).clone();
12493 if let Some(a) = args.first() {
12494 v2.push(a.clone());
12495 }
12496 Ok(Value::List(Rc::new(v2)))
12497 },
12498 (Value::Struct { fields, .. }, _) if args.is_empty() => fields
12500 .iter()
12501 .find(|(k, _)| k == method)
12502 .map(|(_, v)| v.clone())
12503 .ok_or_else(|| EvalErr::from(format!("no field '{method}' on {recv}"))),
12504 (Value::Variant { variant, .. }, "tag" | "标签" | "タグ" | "태그" | "ป้าย")
12506 if args.is_empty() =>
12507 {
12508 Ok(Value::Str(variant.clone()))
12509 },
12510 (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
12511 _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
12512 }
12513 }
12514
12515 fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
12518 match (pat, val) {
12519 (Pattern::Wildcard, _) => Some(new_env()),
12520 (Pattern::Str(s), Value::Str(v)) if s == v => Some(new_env()),
12521 (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(new_env()),
12522 (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(new_env()),
12523 (Pattern::Ident(name), _) => {
12524 let mut e = new_env();
12525 e.insert(name.clone(), val.clone());
12526 Some(e)
12527 },
12528 (Pattern::Constructor(ctor, inner_pat), _) => {
12529 let (matches, inner_val) = match (ctor.as_str(), val) {
12530 ("ok" | "好", Value::Ok(v)) => (true, Some(v.as_ref().clone())),
12531 ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
12532 ("ok" | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
12533 _ => (false, None),
12534 };
12535 if !matches {
12536 return None;
12537 }
12538 match (inner_pat, inner_val) {
12539 (Some(p), Some(v)) => self.match_pattern(p, &v),
12540 (None, _) => Some(new_env()),
12541 (Some(p), None) => self.match_pattern(p, &Value::Unit),
12542 }
12543 },
12544 (Pattern::Variant(vname, sub_pats), Value::Variant { variant, payload, .. }) => {
12546 if vname != variant || sub_pats.len() != payload.len() {
12547 return None;
12548 }
12549 let mut bindings = new_env();
12550 for (p, v) in sub_pats.iter().zip(payload.iter()) {
12551 bindings.extend(self.match_pattern(p, v)?);
12552 }
12553 Some(bindings)
12554 },
12555 (Pattern::Variant(vname, sub), Value::Ok(v)) if (vname == "ok" || vname == "好") => {
12558 match sub.as_slice() {
12559 [] => Some(new_env()),
12560 [p] => self.match_pattern(p, v),
12561 _ => None,
12562 }
12563 },
12564 (Pattern::Variant(vname, sub), Value::Err(v))
12565 if (vname == "bad" || vname == "坏" || vname == "err") =>
12566 {
12567 match sub.as_slice() {
12568 [] => Some(new_env()),
12569 [p] => self.match_pattern(p, v),
12570 _ => None,
12571 }
12572 },
12573 _ => None,
12574 }
12575 }
12576
12577 fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
12580 match val {
12581 Value::List(v) => Ok(Rc::try_unwrap(v).unwrap_or_else(|rc| (*rc).clone())),
12582 Value::Str(s) => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
12583 Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
12584 other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
12585 }
12586 }
12587
12588 pub(crate) fn is_truthy(&self, val: &Value) -> bool {
12589 match val {
12590 Value::Bool(b) => *b,
12591 Value::Unit => false,
12592 Value::Number(n) => *n != 0.0,
12593 Value::Str(s) => !s.is_empty(),
12594 Value::List(v) => !v.is_empty(),
12595 Value::Ok(_) => true,
12596 Value::Err(_) => false,
12597 Value::Fn(_, _, _) => true,
12598 Value::Struct { .. } => true,
12599 Value::Variant { .. } => true,
12600 }
12601 }
12602
12603 fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
12604 match val {
12605 Value::Number(n) => Ok(*n),
12606 Value::Str(s) => s
12607 .parse()
12608 .map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
12609 other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
12610 }
12611 }
12612
12613 fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
12615 match args.get(n) {
12616 Some(v) => self.to_number(v),
12617 None => Ok(default),
12618 }
12619 }
12620
12621 fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
12622 args.get(n)
12623 .map(|v| v.to_string())
12624 .unwrap_or_else(|| default.to_string())
12625 }
12626
12627 #[allow(dead_code)]
12629 fn arg_list_f32(&self, args: &[Value], n: usize) -> Vec<f32> {
12630 match args.get(n) {
12631 Some(Value::List(v)) => v
12632 .iter()
12633 .filter_map(|x| match x {
12634 Value::Number(n) => Some(*n as f32),
12635 _ => None,
12636 })
12637 .collect(),
12638 _ => Vec::new(),
12639 }
12640 }
12641
12642 #[cfg(not(target_arch = "wasm32"))]
12645 fn color_at(&self, args: &[Value], i: usize, default: u32) -> u32 {
12646 match (args.get(i), args.get(i + 1), args.get(i + 2)) {
12647 (Some(a), Some(b), Some(c)) => {
12648 match (self.to_number(a), self.to_number(b), self.to_number(c)) {
12649 (Ok(r), Ok(g), Ok(bl)) => {
12650 ((r as u32 & 0xFF) << 16) | ((g as u32 & 0xFF) << 8) | (bl as u32 & 0xFF)
12651 },
12652 _ => default,
12653 }
12654 },
12655 _ => default,
12656 }
12657 }
12658
12659 #[cfg(not(target_arch = "wasm32"))]
12661 fn pitch_arg(&self, args: &[Value], i: usize, default: i32) -> i32 {
12662 match args.get(i) {
12663 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(default),
12664 Some(Value::Number(n)) => *n as i32,
12665 _ => default,
12666 }
12667 }
12668
12669 #[cfg(not(target_arch = "wasm32"))]
12671 fn mouse_now(&self) -> (f32, f32, bool) {
12672 let gfx = self.gfx.borrow();
12673 let (mx, my) = gfx
12674 .window
12675 .as_ref()
12676 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
12677 .unwrap_or((0.0, 0.0));
12678 let down = gfx
12679 .window
12680 .as_ref()
12681 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
12682 .unwrap_or(false);
12683 (mx, my, down)
12684 }
12685
12686 #[cfg(not(target_arch = "wasm32"))]
12690 fn draw_ui(&self, d: &ling_ui::widgets::Draw) {
12691 let mut gfx = self.gfx.borrow_mut();
12692 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
12693 for (c, poly) in &d.fills {
12694 crate::gfx::raster::fill_contours_aa(
12695 &mut gfx.buffer,
12696 w,
12697 h,
12698 *c,
12699 add,
12700 std::slice::from_ref(poly),
12701 );
12702 }
12703 for (c, pl) in &d.strokes {
12704 for s in pl.windows(2) {
12705 crate::gfx::raster::draw_line_aa(
12706 &mut gfx.buffer,
12707 w,
12708 h,
12709 *c,
12710 add,
12711 s[0][0],
12712 s[0][1],
12713 s[1][0],
12714 s[1][1],
12715 );
12716 }
12717 }
12718 }
12719
12720 fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
12722 let tx = self.arg_num(args, 0, 0.0)? as usize;
12723 let ty = self.arg_num(args, 1, 0.0)? as usize;
12724 let tw = self.arg_num(args, 2, 256.0)? as usize;
12725 let th = self.arg_num(args, 3, 256.0)? as usize;
12726 Ok((tx, ty, tw.max(1), th.max(1)))
12727 }
12728
12729 pub(crate) fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
12730 match op {
12731 BinOp::Add => match (l, r) {
12732 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
12733 (Value::Str(a), Value::Str(b)) => Ok(Value::Str(a + &b)),
12734 (Value::Str(a), b) => Ok(Value::Str(a + &b.to_string())),
12735 (a, Value::Str(b)) => Ok(Value::Str(a.to_string() + &b)),
12736 (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
12737 },
12738 BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
12739 BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
12740 BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
12741 BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
12742 BinOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
12743 BinOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
12744 BinOp::Lt => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
12745 BinOp::Gt => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
12746 BinOp::Le => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
12747 BinOp::Ge => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
12748 BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
12749 BinOp::Or => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
12750 }
12751 }
12752
12753 fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
12754 if args.is_empty() {
12755 return Ok(String::new());
12756 }
12757 let fmt = match &args[0] {
12758 Value::Str(s) => s.clone(),
12759 other => return Ok(other.to_string()),
12760 };
12761
12762 let mut result = String::new();
12763 let mut arg_idx = 1usize;
12764 let mut chars = fmt.chars().peekable();
12765 while let Some(c) = chars.next() {
12766 if c == '{' {
12767 if chars.peek() == Some(&'}') {
12768 chars.next();
12769 if arg_idx < args.len() {
12770 result.push_str(&args[arg_idx].to_string());
12771 arg_idx += 1;
12772 }
12773 } else {
12774 let mut spec = String::new();
12775 for ch in chars.by_ref() {
12776 if ch == '}' {
12777 break;
12778 }
12779 spec.push(ch);
12780 }
12781 if arg_idx < args.len() {
12782 if let Some(suffix) = spec.strip_prefix(":.") {
12783 if let Value::Number(n) = &args[arg_idx] {
12784 let prec: usize =
12785 suffix.trim_end_matches('f').parse().unwrap_or(2);
12786 result.push_str(&format!("{:.prec$}", n));
12787 arg_idx += 1;
12788 continue;
12789 }
12790 }
12791 result.push_str(&args[arg_idx].to_string());
12792 arg_idx += 1;
12793 }
12794 }
12795 } else {
12796 result.push(c);
12797 }
12798 }
12799 Ok(result)
12800 }
12801}
12802
12803#[cfg(not(target_arch = "wasm32"))]
12804#[cfg(not(target_arch = "wasm32"))]
12806fn parse_pad_button(name: &str) -> Option<ling_input::GamepadButton> {
12807 use ling_input::GamepadButton as B;
12808 Some(match name.to_ascii_lowercase().as_str() {
12809 "a" | "south" | "cross" => B::South,
12810 "b" | "east" | "circle" => B::East,
12811 "x" | "west" | "square" => B::West,
12812 "y" | "north" | "triangle" => B::North,
12813 "lb" | "l1" | "left_shoulder" => B::LeftShoulder,
12814 "rb" | "r1" | "right_shoulder" => B::RightShoulder,
12815 "lt" | "l2" | "left_trigger" => B::LeftTrigger,
12816 "rt" | "r2" | "right_trigger" => B::RightTrigger,
12817 "start" | "menu" | "options" => B::Start,
12818 "select" | "back" | "share" | "view" => B::Select,
12819 "guide" | "home" => B::Guide,
12820 "l3" | "left_stick" => B::LeftStick,
12821 "r3" | "right_stick" => B::RightStick,
12822 "up" | "dpad_up" => B::DpadUp,
12823 "down" | "dpad_down" => B::DpadDown,
12824 "left" | "dpad_left" => B::DpadLeft,
12825 "right" | "dpad_right" => B::DpadRight,
12826 _ => return None,
12827 })
12828}
12829
12830#[cfg(not(target_arch = "wasm32"))]
12831fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
12832 use minifb::Key;
12833 Some(match name {
12834 "numpad0" | "kp0" => Key::NumPad0,
12835 "numpad1" | "kp1" => Key::NumPad1,
12836 "numpad2" | "kp2" => Key::NumPad2,
12837 "numpad3" | "kp3" => Key::NumPad3,
12838 "numpad4" | "kp4" => Key::NumPad4,
12839 "numpad5" | "kp5" => Key::NumPad5,
12840 "numpad6" | "kp6" => Key::NumPad6,
12841 "numpad7" | "kp7" => Key::NumPad7,
12842 "numpad8" | "kp8" => Key::NumPad8,
12843 "numpad9" | "kp9" => Key::NumPad9,
12844 "numpad+" | "kp+" => Key::NumPadPlus,
12845 "numpad-" | "kp-" => Key::NumPadMinus,
12846 "numpad*" | "kp*" => Key::NumPadAsterisk,
12847 "numpad/" | "kp/" => Key::NumPadSlash,
12848 "left" => Key::Left,
12849 "right" => Key::Right,
12850 "up" => Key::Up,
12851 "down" => Key::Down,
12852 "space" => Key::Space,
12853 "enter" => Key::Enter,
12854 "escape" => Key::Escape,
12855 "pageup" => Key::PageUp,
12856 "pagedown" => Key::PageDown,
12857 "lshift" | "leftshift" => Key::LeftShift,
12858 "rshift" | "rightshift" => Key::RightShift,
12859 "lctrl" | "leftctrl" => Key::LeftCtrl,
12860 "rctrl" | "rightctrl" => Key::RightCtrl,
12861 "lalt" | "leftalt" => Key::LeftAlt,
12862 "ralt" | "rightalt" => Key::RightAlt,
12863 "tab" => Key::Tab,
12864 "backspace" => Key::Backspace,
12865 "delete" => Key::Delete,
12866 "insert" => Key::Insert,
12867 "home" => Key::Home,
12868 "end" => Key::End,
12869 "a" => Key::A,
12870 "b" => Key::B,
12871 "c" => Key::C,
12872 "d" => Key::D,
12873 "e" => Key::E,
12874 "f" => Key::F,
12875 "g" => Key::G,
12876 "h" => Key::H,
12877 "i" => Key::I,
12878 "j" => Key::J,
12879 "k" => Key::K,
12880 "l" => Key::L,
12881 "m" => Key::M,
12882 "n" => Key::N,
12883 "o" => Key::O,
12884 "p" => Key::P,
12885 "q" => Key::Q,
12886 "r" => Key::R,
12887 "s" => Key::S,
12888 "t" => Key::T,
12889 "u" => Key::U,
12890 "v" => Key::V,
12891 "w" => Key::W,
12892 "x" => Key::X,
12893 "y" => Key::Y,
12894 "z" => Key::Z,
12895 "0" => Key::Key0,
12896 "1" => Key::Key1,
12897 "2" => Key::Key2,
12898 "3" => Key::Key3,
12899 "4" => Key::Key4,
12900 "5" => Key::Key5,
12901 "6" => Key::Key6,
12902 "7" => Key::Key7,
12903 "8" => Key::Key8,
12904 "9" => Key::Key9,
12905 _ => return None,
12906 })
12907}
12908
12909pub(crate) fn values_equal(a: &Value, b: &Value) -> bool {
12910 match (a, b) {
12911 (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
12912 (Value::Str(x), Value::Str(y)) => x == y,
12913 (Value::Bool(x), Value::Bool(y)) => x == y,
12914 (Value::Unit, Value::Unit) => true,
12915 _ => false,
12916 }
12917}
12918
12919#[cfg(not(target_arch = "wasm32"))]
12926fn hide_console_window() {
12927 #[cfg(windows)]
12928 unsafe {
12929 extern "system" {
12930 fn GetConsoleWindow() -> isize;
12931 fn ShowWindow(hwnd: isize, nCmdShow: i32) -> i32;
12932 }
12933 let hwnd = GetConsoleWindow();
12934 if hwnd != 0 {
12935 ShowWindow(hwnd, 0); }
12937 }
12938}
12939
12940#[cfg(all(not(target_arch = "wasm32"), windows))]
12945fn make_borderless_fullscreen(hwnd: isize, screen_w: i32, screen_h: i32) {
12946 if hwnd == 0 {
12947 return;
12948 }
12949 unsafe {
12950 extern "system" {
12951 fn SetWindowLongPtrW(hwnd: isize, index: i32, new: isize) -> isize;
12952 fn SetWindowPos(
12953 hwnd: isize,
12954 insert_after: isize,
12955 x: i32,
12956 y: i32,
12957 cx: i32,
12958 cy: i32,
12959 flags: u32,
12960 ) -> i32;
12961 fn ShowWindow(hwnd: isize, cmd: i32) -> i32;
12962 }
12963 const GWL_STYLE: i32 = -16;
12964 const GWL_EXSTYLE: i32 = -20;
12965 SetWindowLongPtrW(hwnd, GWL_STYLE, 0x9000_0000isize);
12968 SetWindowLongPtrW(hwnd, GWL_EXSTYLE, 0);
12970 SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0020 | 0x0040);
12972 ShowWindow(hwnd, 3); }
12974}
12975
12976#[cfg(all(not(target_arch = "wasm32"), windows))]
12990fn force_window_focus(hwnd: isize) {
12991 if hwnd == 0 {
12992 return;
12993 }
12994 unsafe {
12995 extern "system" {
12996 fn GetForegroundWindow() -> isize;
12997 fn GetWindowThreadProcessId(hwnd: isize, pid: *mut u32) -> u32;
12998 fn GetCurrentThreadId() -> u32;
12999 fn AttachThreadInput(id_attach: u32, id_attach_to: u32, attach: i32) -> i32;
13000 fn SetForegroundWindow(hwnd: isize) -> i32;
13001 fn BringWindowToTop(hwnd: isize) -> i32;
13002 fn SetFocus(hwnd: isize) -> isize;
13003 fn SetActiveWindow(hwnd: isize) -> isize;
13004 }
13005 let fg = GetForegroundWindow();
13006 if fg != 0 && fg != hwnd {
13007 let mut fg_pid: u32 = 0;
13008 let fg_tid = GetWindowThreadProcessId(fg, &mut fg_pid);
13009 let my_tid = GetCurrentThreadId();
13010 if fg_tid != 0 && fg_tid != my_tid {
13011 AttachThreadInput(my_tid, fg_tid, 1);
13012 SetForegroundWindow(hwnd);
13013 BringWindowToTop(hwnd);
13014 SetFocus(hwnd);
13015 SetActiveWindow(hwnd);
13016 AttachThreadInput(my_tid, fg_tid, 0);
13017 return;
13018 }
13019 }
13020 SetForegroundWindow(hwnd);
13021 BringWindowToTop(hwnd);
13022 SetFocus(hwnd);
13023 SetActiveWindow(hwnd);
13024 }
13025}
13026
13027#[cfg(all(not(target_arch = "wasm32"), windows))]
13032fn set_window_topmost(hwnd: isize, topmost: bool) {
13033 if hwnd == 0 {
13034 return;
13035 }
13036 unsafe {
13037 extern "system" {
13038 fn SetWindowPos(
13039 hwnd: isize,
13040 insert_after: isize,
13041 x: i32,
13042 y: i32,
13043 cx: i32,
13044 cy: i32,
13045 flags: u32,
13046 ) -> i32;
13047 }
13048 let insert_after: isize = if topmost { -1 } else { -2 }; SetWindowPos(hwnd, insert_after, 0, 0, 0, 0, 0x0002 | 0x0001 | 0x0010);
13052 }
13053}
13054
13055#[cfg(not(target_arch = "wasm32"))]
13061fn apply_frame_pacing(win: &mut minifb::Window, vsync: bool) {
13062 match std::env::var("LING_FPS_CAP")
13063 .ok()
13064 .and_then(|v| v.parse::<usize>().ok())
13065 {
13066 Some(0) => win.set_target_fps(100_000),
13067 Some(cap) => win.set_target_fps(cap),
13068 None if vsync => win.set_target_fps(monitor_info().2.max(30) as usize),
13069 None => win.set_target_fps(100_000),
13070 }
13071}
13072
13073#[cfg(all(not(target_arch = "wasm32"), windows))]
13076fn monitor_info() -> (i32, i32, i32) {
13077 unsafe {
13078 extern "system" {
13079 fn GetSystemMetrics(index: i32) -> i32;
13080 fn GetDC(hwnd: isize) -> isize;
13081 fn ReleaseDC(hwnd: isize, hdc: isize) -> i32;
13082 fn GetDeviceCaps(hdc: isize, index: i32) -> i32;
13083 }
13084 let w = GetSystemMetrics(0).max(1); let h = GetSystemMetrics(1).max(1); let hdc = GetDC(0);
13087 let mut hz = if hdc != 0 { GetDeviceCaps(hdc, 116) } else { 0 }; if hdc != 0 {
13089 ReleaseDC(0, hdc);
13090 }
13091 if hz <= 1 {
13092 hz = 60; }
13094 (w, h, hz)
13095 }
13096}
13097
13098#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13102fn monitor_info() -> (i32, i32, i32) {
13103 let (w, h) = native_screen_size();
13104 (w as i32, h as i32, linux_refresh_hz().unwrap_or(60))
13105}
13106
13107#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13112fn linux_refresh_hz() -> Option<i32> {
13113 let out = std::process::Command::new("xrandr")
13114 .arg("--current")
13115 .output()
13116 .ok()?;
13117 if !out.status.success() {
13118 return None;
13119 }
13120 parse_xrandr_max_hz(&String::from_utf8_lossy(&out.stdout))
13121}
13122
13123#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13126fn parse_xrandr_max_hz(text: &str) -> Option<i32> {
13127 text.split_whitespace()
13128 .filter(|tok| tok.contains('*'))
13129 .filter_map(|tok| {
13130 tok.trim_matches(|c: char| !c.is_ascii_digit() && c != '.')
13131 .parse::<f64>()
13132 .ok()
13133 })
13134 .map(|hz| hz.round() as i32)
13135 .filter(|&hz| (24..=1000).contains(&hz))
13136 .max()
13137}
13138
13139#[cfg(target_arch = "wasm32")]
13141fn monitor_info() -> (i32, i32, i32) {
13142 let (w, h) = crate::gfx::webgl::canvas_size();
13143 (w as i32, h as i32, 60)
13144}
13145
13146#[cfg(all(test, not(target_arch = "wasm32"), not(windows)))]
13147mod xrandr_tests {
13148 use super::parse_xrandr_max_hz;
13149
13150 #[test]
13151 fn picks_highest_active_output() {
13152 let text = "\
13153eDP-1 connected primary 1920x1080+0+0
13154 1920x1080 60.00*+ 59.94
13155DP-1 connected 2560x1440+1920+0
13156 2560x1440 144.00*+ 120.00 60.00
13157";
13158 assert_eq!(parse_xrandr_max_hz(text), Some(144));
13159 }
13160
13161 #[test]
13162 fn single_output() {
13163 let text = " 1920x1080 75.00*+ 60.00\n";
13164 assert_eq!(parse_xrandr_max_hz(text), Some(75));
13165 }
13166
13167 #[test]
13168 fn no_active_mode_returns_none() {
13169 let text = "eDP-1 disconnected\n";
13170 assert_eq!(parse_xrandr_max_hz(text), None);
13171 }
13172
13173 #[test]
13174 fn out_of_range_hz_filtered() {
13175 let text = " 1x1 5000.00*+\n";
13176 assert_eq!(parse_xrandr_max_hz(text), None);
13177 }
13178}
13179
13180#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13183fn native_screen_size() -> (f64, f64) {
13184 (1920.0, 1080.0)
13187}
13188
13189struct LingProfileState {
13201 enabled: bool,
13202 every: u64,
13203 frames: u64,
13204 calls: std::collections::HashMap<String, (u64, u128)>, }
13206
13207thread_local! {
13208 static LING_PROFILE: std::cell::RefCell<LingProfileState> = std::cell::RefCell::new({
13209 let enabled = std::env::var("LING_PROFILE").map(|v| v != "0" && !v.is_empty()).unwrap_or(false);
13210 let every = std::env::var("LING_PROFILE_EVERY").ok()
13211 .and_then(|v| v.parse::<u64>().ok()).filter(|&n| n > 0).unwrap_or(240);
13212 if enabled {
13213 eprintln!("[ling-profile] ON — report every {every} frames (set LING_PROFILE_EVERY to change)");
13214 }
13215 LingProfileState { enabled, every, frames: 0, calls: std::collections::HashMap::new() }
13216 });
13217}
13218
13219#[inline]
13220fn ling_profile_enabled() -> bool {
13221 LING_PROFILE.with(|p| p.borrow().enabled)
13222}
13223
13224thread_local! {
13225 static LING_FPS: std::cell::RefCell<(bool, f64, u32, f64)> = std::cell::RefCell::new(
13226 (std::env::var("LING_FPS").map(|v| v != "0" && !v.is_empty()).unwrap_or(false), 0.0, 0, 0.0)
13227 );
13228}
13229
13230#[cfg(not(target_arch = "wasm32"))]
13231fn ling_fps_tick() {
13232 LING_FPS.with(|s| {
13233 let mut s = s.borrow_mut();
13234 if !s.0 {
13235 return;
13236 }
13237 let now = crate::runtime::now_secs();
13238 if s.1 > 0.0 {
13239 s.3 += now - s.1;
13240 s.2 += 1;
13241 if s.2 >= 120 {
13242 let avg = s.3 / s.2 as f64;
13243 eprintln!(
13244 "[fps] {:.1} fps ({:.2} ms/frame, wall, {} frames)",
13245 1.0 / avg,
13246 avg * 1000.0,
13247 s.2
13248 );
13249 s.2 = 0;
13250 s.3 = 0.0;
13251 }
13252 }
13253 s.1 = now;
13254 });
13255}
13256
13257thread_local! {
13262 static LING_PHASE: std::cell::RefCell<(bool, u64, [u128; 5])> = std::cell::RefCell::new(
13263 (std::env::var_os("LING_PHASE").is_some(), 0, [0; 5])
13264 );
13265}
13266
13267pub mod phase {
13269 pub const FLUSH: usize = 0;
13270 pub const TOON: usize = 1;
13271 pub const BLIT: usize = 2;
13272 pub const DISTORT: usize = 3;
13273 pub const SORT: usize = 4;
13274}
13275
13276#[cfg(not(target_arch = "wasm32"))]
13277#[inline]
13278pub fn ling_phase_add(idx: usize, nanos: u128) {
13279 LING_PHASE.with(|p| {
13280 let mut p = p.borrow_mut();
13281 if p.0 {
13282 p.2[idx] += nanos;
13283 }
13284 });
13285}
13286
13287#[cfg(not(target_arch = "wasm32"))]
13288fn ling_phase_frame() {
13289 LING_PHASE.with(|p| {
13290 let mut p = p.borrow_mut();
13291 if !p.0 {
13292 return;
13293 }
13294 p.1 += 1;
13295 if p.1 >= 120 {
13296 let f = p.1 as f64;
13297 let ms = |i: usize| p.2[i] as f64 / 1e6 / f;
13298 eprintln!(
13299 "[phase] sort={:.2} flush={:.2} toon={:.2} blit={:.2} distort={:.2} ms/frame",
13300 ms(phase::SORT),
13301 ms(phase::FLUSH),
13302 ms(phase::TOON),
13303 ms(phase::BLIT),
13304 ms(phase::DISTORT)
13305 );
13306 p.1 = 0;
13307 p.2 = [0; 5];
13308 }
13309 });
13310}
13311
13312fn ling_profile_record(name: &str, nanos: u128) {
13313 let is_frame = matches!(
13315 name,
13316 "present" | "แสดงผล" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
13317 );
13318 LING_PROFILE.with(|p| {
13319 let mut p = p.borrow_mut();
13320 let e = p.calls.entry(name.to_string()).or_insert((0, 0));
13321 e.0 += 1;
13322 e.1 += nanos;
13323 if is_frame {
13324 p.frames += 1;
13325 if p.frames % p.every == 0 {
13326 ling_profile_print(&p);
13327 }
13328 }
13329 });
13330}
13331
13332fn ling_profile_print(p: &LingProfileState) {
13333 let mut rows: Vec<(&String, u64, u128)> =
13334 p.calls.iter().map(|(n, (c, ns))| (n, *c, *ns)).collect();
13335 use std::cmp::Reverse;
13336 rows.sort_by_key(|x| Reverse(x.2)); let total_ns: u128 = p.calls.values().map(|(_, ns)| *ns).sum();
13338 let total_calls: u64 = p.calls.values().map(|(c, _)| *c).sum();
13339 let fr = p.frames.max(1) as f64;
13340 eprintln!(
13341 "\n┌─ LING PROFILE ── frames={} ─ builtin calls by total inclusive time ─────────────",
13342 p.frames
13343 );
13344 eprintln!(
13345 "│ {:<24} {:>9} {:>9} {:>10} {:>9} {:>6}",
13346 "builtin", "calls", "calls/fr", "total_ms", "ms/frame", "%time"
13347 );
13348 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13349 for (name, count, ns) in rows.iter().take(30) {
13350 let ms = *ns as f64 / 1e6;
13351 let pct = if total_ns > 0 {
13352 *ns as f64 / total_ns as f64 * 100.0
13353 } else {
13354 0.0
13355 };
13356 eprintln!(
13357 "│ {:<24} {:>9} {:>9.1} {:>10.1} {:>9.3} {:>5.1}%",
13358 truncate_name(name),
13359 count,
13360 *count as f64 / fr,
13361 ms,
13362 ms / fr,
13363 pct
13364 );
13365 }
13366 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13367 eprintln!(
13368 "│ TOTAL {} builtin calls, {:.1} ms over {} frames → {:.0} calls/frame, {:.2} ms/frame in builtins",
13369 total_calls,
13370 total_ns as f64 / 1e6,
13371 p.frames,
13372 total_calls as f64 / fr,
13373 total_ns as f64 / 1e6 / fr
13374 );
13375 eprintln!("└──────────────────────────────────────────────────────────────────────────────");
13376}
13377
13378fn truncate_name(s: &str) -> String {
13381 let max = 24;
13382 if s.chars().count() <= max {
13383 s.to_string()
13384 } else {
13385 let mut t: String = s.chars().take(max - 1).collect();
13386 t.push('…');
13387 t
13388 }
13389}