1#[cfg(not(target_arch = "wasm32"))]
3mod ai;
4#[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
5mod llm;
6#[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
7mod vision;
8#[cfg(not(target_arch = "wasm32"))]
9mod gamepad;
10#[cfg(target_arch = "wasm32")]
11mod input_web;
12#[cfg(not(target_arch = "wasm32"))]
13pub(crate) mod jit_abi;
14
15#[cfg(not(target_arch = "wasm32"))]
18pub fn init_aot_runtime() {
19 let interp = Interpreter::new();
20 jit_abi::init(interp);
21}
22
23pub fn now_secs() -> f64 {
26 #[cfg(target_arch = "wasm32")]
27 {
28 js_sys::Date::now() / 1000.0
29 }
30 #[cfg(not(target_arch = "wasm32"))]
31 {
32 std::time::SystemTime::now()
33 .duration_since(std::time::UNIX_EPOCH)
34 .map(|d| d.as_secs_f64())
35 .unwrap_or(0.0)
36 }
37}
38
39static LINE_HUE_RATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
43static LINE_HUE_START: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
44
45pub fn set_line_hue_rate(rate: f64) {
47 LINE_HUE_RATE.store(rate.to_bits(), std::sync::atomic::Ordering::Relaxed);
48 LINE_HUE_START.store(now_secs().to_bits(), std::sync::atomic::Ordering::Relaxed);
49}
50
51pub fn line_hue_phase() -> Option<f64> {
55 let rate = f64::from_bits(LINE_HUE_RATE.load(std::sync::atomic::Ordering::Relaxed));
56 if rate <= 0.0 {
57 return None;
58 }
59 let start = f64::from_bits(LINE_HUE_START.load(std::sync::atomic::Ordering::Relaxed));
60 Some((now_secs() - start) * rate)
61}
62
63#[cfg(target_arch = "wasm32")]
66thread_local! {
67 static WASM_MODULES: std::cell::RefCell<std::collections::HashMap<String, String>> =
68 std::cell::RefCell::new(std::collections::HashMap::new());
69}
70
71#[cfg(target_arch = "wasm32")]
74pub fn register_wasm_module(path: &str, source: &str) {
75 WASM_MODULES.with(|m| m.borrow_mut().insert(path.to_string(), source.to_string()));
76}
77
78#[cfg(target_arch = "wasm32")]
80pub(crate) fn get_wasm_module(path: &str) -> Option<String> {
81 WASM_MODULES.with(|m| m.borrow().get(path).cloned())
82}
83
84#[cfg(target_arch = "wasm32")]
85#[inline]
86fn wasm_sleep_ms(ms: i32) {
87 if ms <= 0 {
88 return;
89 }
90
91 let global = js_sys::global();
94 let has_sab = js_sys::Reflect::has(
95 &global,
96 &wasm_bindgen::JsValue::from_str("SharedArrayBuffer"),
97 )
98 .unwrap_or(false);
99 let has_atomics =
100 js_sys::Reflect::has(&global, &wasm_bindgen::JsValue::from_str("Atomics")).unwrap_or(false);
101 if has_sab && has_atomics {
102 let sab = js_sys::SharedArrayBuffer::new(4);
103 let i32a = js_sys::Int32Array::new(&sab);
104 if js_sys::Atomics::wait_with_timeout(&i32a, 0, 0, ms as f64).is_ok() {
105 return;
106 }
107 }
108
109 let end = js_sys::Date::now() + ms as f64;
110 while js_sys::Date::now() < end {}
111}
112
113#[cfg(target_arch = "wasm32")]
114fn wasm_fetch_sync(
115 path: &str,
116 response_type: &str,
117 return_expr: &str,
118) -> Result<wasm_bindgen::JsValue, String> {
119 let quoted = js_sys::JSON::stringify(&wasm_bindgen::JsValue::from_str(path))
120 .ok()
121 .and_then(|s| s.as_string())
122 .unwrap_or_else(|| "\"\"".to_string());
123
124 let script = format!(
125 "(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}})()"
126 );
127
128 js_sys::eval(&script).map_err(|e| {
129 e.as_string()
130 .unwrap_or_else(|| format!("JS eval failed: {:?}", e))
131 })
132}
133
134#[cfg(target_arch = "wasm32")]
135fn wasm_fetch_bytes(path: &str) -> Result<Vec<u8>, String> {
136 let value = wasm_fetch_sync(
137 path,
138 "arraybuffer",
139 "new Uint8Array(xhr.response || new ArrayBuffer(0))",
140 )?;
141 let arr = js_sys::Uint8Array::new(&value);
142 let mut out = vec![0u8; arr.length() as usize];
143 arr.copy_to(&mut out);
144 Ok(out)
145}
146
147#[cfg(target_arch = "wasm32")]
148fn wasm_fetch_text(path: &str) -> Result<String, String> {
149 let value = wasm_fetch_sync(path, "text", "String(xhr.responseText || '')")?;
150 Ok(value.as_string().unwrap_or_default())
151}
152
153#[cfg(not(target_arch = "wasm32"))]
154mod net;
155#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
156pub mod web;
157use crate::gfx::{GfxState, Light};
158use crate::parser::ast::*;
159#[cfg(target_arch = "wasm32")]
160use js_sys;
161use std::cell::RefCell;
162use std::collections::HashMap;
163use std::rc::Rc;
164use crate::gfx::raster::draw_line;
167#[cfg(not(target_arch = "wasm32"))]
168use crate::gfx::raster::fill_triangle;
169#[cfg(not(target_arch = "wasm32"))]
170use ling_audio::{AudioEngine, ToneParams, Wave};
171
172#[cfg(not(target_arch = "wasm32"))]
173use ling_audio::FftAnalyzer;
174
175#[cfg(not(target_arch = "wasm32"))]
176use ling_mic;
177
178#[derive(Debug, Clone)]
181pub enum Value {
182 Str(String),
183 Number(f64),
184 Bool(bool),
185 Unit,
186 List(Rc<Vec<Value>>),
187 Ok(Box<Value>),
188 Err(Box<Value>),
189 Fn(Vec<String>, Vec<Stmt>, Env),
190 Struct {
192 name: String,
193 fields: Vec<(String, Value)>,
194 },
195 Variant {
197 enum_name: String,
198 variant: String,
199 payload: Vec<Value>,
200 },
201}
202
203use rustc_hash::FxHashMap;
206type Env = FxHashMap<String, Value>;
207
208#[inline]
209fn new_env() -> Env {
210 FxHashMap::default()
211}
212
213impl std::fmt::Display for Value {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 match self {
216 Value::Str(s) => write!(f, "{s}"),
217 Value::Number(n) => {
218 if n.fract() == 0.0 && n.abs() < 1e15 {
219 write!(f, "{}", *n as i64)
220 } else {
221 write!(f, "{n}")
222 }
223 },
224 Value::Bool(b) => write!(f, "{b}"),
225 Value::Unit => write!(f, "()"),
226 Value::List(v) => {
227 write!(f, "[")?;
228 for (i, x) in v.iter().enumerate() {
229 if i > 0 {
230 write!(f, ", ")?;
231 }
232 write!(f, "{x}")?;
233 }
234 write!(f, "]")
235 },
236 Value::Ok(v) => write!(f, "Ok({v})"),
237 Value::Err(v) => write!(f, "Err({v})"),
238 Value::Fn(_, _, _) => write!(f, "<fn>"),
239 Value::Struct { name, fields } => {
240 write!(f, "{name} {{ ")?;
241 for (i, (k, v)) in fields.iter().enumerate() {
242 if i > 0 {
243 write!(f, ", ")?;
244 }
245 write!(f, "{k}: {v}")?;
246 }
247 write!(f, " }}")
248 },
249 Value::Variant { variant, payload, .. } => {
250 write!(f, "{variant}")?;
251 if !payload.is_empty() {
252 write!(f, "(")?;
253 for (i, v) in payload.iter().enumerate() {
254 if i > 0 {
255 write!(f, ", ")?;
256 }
257 write!(f, "{v}")?;
258 }
259 write!(f, ")")?;
260 }
261 Ok(())
262 },
263 }
264 }
265}
266
267#[cfg(target_arch = "wasm32")]
268fn wasm_unsupported_builtin(name: &str) -> Option<Value> {
269 Some(match name {
270 "audio_blip"
272 | "提示音"
273 | "ビープ音"
274 | "효과음"
275 | "เสียงบี๊บ"
276 | "ui_sound"
277 | "界面音"
278 | "UI音"
279 | "인터페이스음"
280 | "เสียงปุ่ม"
281 | "audio_stop_sfx"
282 | "停止音效"
283 | "効果音停止"
284 | "효과음정지"
285 | "หยุดเอฟเฟกต์ทั้งหมด" | "بوق_کوتاه" | "نغمة_قصيرة" | "ביפ" | "بلپ_آواز" => Value::Unit,
286
287 "music_load"
289 | "载入音乐"
290 | "音楽読込"
291 | "음악로드"
292 | "โหลดเพลง"
293 | "music_patch"
294 | "乐器音色"
295 | "音色読込"
296 | "악기패치"
297 | "แพตช์เครื่องดนตรี"
298 | "music_lrc"
299 | "载入歌词"
300 | "歌詞読込"
301 | "가사로드"
302 | "โหลดเนื้อเพลง"
303 | "music_midi_load"
304 | "载入MIDI"
305 | "MIDI読込"
306 | "미디로드"
307 | "โหลดมิดี" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" => Value::Number(-1.0),
308
309 "music_duration"
310 | "音乐时长"
311 | "音楽長さ"
312 | "음악길이"
313 | "ความยาวเพลง"
314 | "music_bpm"
315 | "节拍速度"
316 | "テンポ"
317 | "템포"
318 | "จังหวะต่อนาที"
319 | "music_pos"
320 | "音乐位置"
321 | "音楽位置"
322 | "음악위치"
323 | "ตำแหน่งเพลง"
324 | "music_mic_pitch"
325 | "麦克风音高"
326 | "マイク音程"
327 | "마이크음정"
328 | "ระดับเสียงไมค์"
329 | "music_hz"
330 | "音符频率"
331 | "音符周波数"
332 | "음표주파수"
333 | "ความถี่โน้ต"
334 | "music_pitch_score"
335 | "音准评分"
336 | "音程スコア"
337 | "음정점수"
338 | "คะแนนเสียง"
339 | "music_judge"
340 | "判定"
341 | "判定する"
342 | "판정"
343 | "ตัดสินจังหวะ"
344 | "music_midi_count"
345 | "MIDI数量"
346 | "MIDI数"
347 | "미디수"
348 | "จำนวนมิดี" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" => Value::Number(0.0),
349
350 "music_key"
351 | "调性"
352 | "調性"
353 | "조성"
354 | "คีย์เพลง"
355 | "music_lyric"
356 | "当前歌词"
357 | "現在歌詞"
358 | "현재가사"
359 | "เนื้อเพลงปัจจุบัน"
360 | "music_note_name"
361 | "音名"
362 | "音名称"
363 | "음이름"
364 | "ชื่อโน้ต"
365 | "music_grade_name"
366 | "判定名"
367 | "判定名称"
368 | "판정이름"
369 | "ชื่อการตัดสิน" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" => Value::Str(String::new()),
370
371 "music_onsets"
372 | "音符起点"
373 | "オンセット"
374 | "온셋"
375 | "จุดเริ่มเสียง"
376 | "music_beat_grid"
377 | "节拍网格"
378 | "ビートグリッド"
379 | "비트그리드"
380 | "กริดจังหวะ"
381 | "music_midi_notes"
382 | "MIDI音符"
383 | "MIDIノート"
384 | "미디음표"
385 | "โน้ตมิดี"
386 | "music_midi_bars"
387 | "MIDI音条"
388 | "MIDIバー"
389 | "미디바"
390 | "แท่งมิดี"
391 | "music_fft"
392 | "音乐频谱"
393 | "音楽スペクトル"
394 | "음악스펙트럼"
395 | "สเปกตรัมเพลง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" => Value::List(Vec::new().into()),
396
397 "music_play"
398 | "播放音乐"
399 | "音楽再生"
400 | "음악재생"
401 | "เล่นเพลง"
402 | "music_pause"
403 | "暂停音乐"
404 | "音楽一時停止"
405 | "음악일시정지"
406 | "หยุดเพลงชั่วคราว"
407 | "music_stop"
408 | "停止音乐"
409 | "音楽停止"
410 | "음악정지"
411 | "หยุดเพลง"
412 | "music_seek"
413 | "定位音乐"
414 | "音楽シーク"
415 | "음악탐색"
416 | "ค้นหาเพลง"
417 | "music_volume"
418 | "音乐音量"
419 | "音楽音量"
420 | "음악음량"
421 | "ระดับเพลง"
422 | "music_note"
423 | "弹音符"
424 | "音符演奏"
425 | "음표연주"
426 | "เล่นโน้ต"
427 | "music_note_on"
428 | "音符开始"
429 | "音符オン"
430 | "음표켜기"
431 | "โน้ตเริ่ม"
432 | "music_note_off"
433 | "音符结束"
434 | "音符オフ"
435 | "음표끄기"
436 | "โน้ตจบ" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" => Value::Unit,
437
438 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" | "مایع_جدید" | "سائل_جديد" | "נוזל_חדש" | "نیا_مائع" => {
440 Value::Number(0.0)
441 },
442 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" | "ترکیب_مایع" | "مزج_سائل" | "ערבוב_נוזל" | "مائع_ملاؤ" => {
443 Value::Number(0.0)
444 },
445 "liquid_set_colors"
446 | "液体颜色"
447 | "液体配色"
448 | "액체색상"
449 | "สีของเหลว"
450 | "liquid_splat"
451 | "液体注入"
452 | "液体追加"
453 | "액체분사"
454 | "หยดของเหลว"
455 | "liquid_gravity"
456 | "液体重力"
457 | "液体重力ベクトル"
458 | "액체중력"
459 | "แรงโน้มถ่วงเหลว"
460 | "liquid_step"
461 | "液体步进"
462 | "液体更新"
463 | "액체스텝"
464 | "ก้าวของเหลว"
465 | "liquid_step_all"
466 | "液体全步进"
467 | "液体全更新"
468 | "전체액체스텝"
469 | "ก้าวของเหลวทั้งหมด"
470 | "liquid_rainbow"
471 | "液体彩虹"
472 | "液体虹"
473 | "액체무지개"
474 | "ของเหลวสายรุ้ง"
475 | "liquid_draw"
476 | "绘制液体"
477 | "液体描画"
478 | "액체그리기"
479 | "วาดของเหลว"
480 | "liquid_draw_surface"
481 | "液体贴面"
482 | "液体曲面"
483 | "액체곡면"
484 | "ของเหลวบนพื้นผิว" | "تنظیم_رنگ_مایع" | "عيّن_ألوان_السائل" | "קבע_צבעי_נוזל" | "مائع_رنگ_مقرر_کرو" => Value::Unit,
485
486 "nn_new"
488 | "建神经网"
489 | "ニューラル作成"
490 | "신경망생성"
491 | "สร้างโครงข่าย"
492 | "nn_load"
493 | "载入网"
494 | "網読込"
495 | "신경망불러오기"
496 | "โหลดโครงข่าย" | "شبکه_جدید" | "شبكة_جديدة" | "רשת_חדשה" | "نئی_نیورل_نیٹ" => Value::Number(-1.0),
497 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" | "پیشروی_شبکه" | "تمرير_أمامي" | "העברה_קדימה" | "فارورڈ_پاس" => {
498 Value::List(Vec::new().into())
499 },
500 "nn_train"
501 | "训练网"
502 | "ニューラル学習"
503 | "신경망학습"
504 | "ฝึกโครงข่าย"
505 | "nn_dense"
506 | "密集层"
507 | "密層追加"
508 | "밀집층"
509 | "ชั้นหนาแน่น" | "آموزش_شبکه" | "درّب_الشبكة" | "אמן_רשת" | "نیٹ_ٹریننگ" => Value::Number(0.0),
510 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" | "ذخیره_شبکه" | "احفظ_الشبكة" | "שמור_רשת" | "نیٹ_محفوظ_کرو" => {
511 Value::Bool(false)
512 },
513
514 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" | "ساخت_درخت_رفتار" | "ابنِ_شجرة_السلوك" | "בנה_עץ_התנהגות" | "بی_ٹی_تعمیر" => {
516 Value::Number(-1.0)
517 },
518 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" | "تیک_درخت_رفتار" | "نبضة_شجرة_السلوك" | "טיק_עץ_התנהגות" | "بی_ٹی_ٹک" => {
519 Value::Str(String::new())
520 },
521 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" | "وضعیت_درخت_رفتار" | "حالة_شجرة_السلوك" | "סטטוס_עץ_התנהגות" | "بی_ٹی_حالت" => {
522 Value::Number(0.0)
523 },
524 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" | "تنظیم_واقعیت" | "عيّن_حقيقة" | "קבע_עובדה" | "بی_ٹی_سیٹ" => {
525 Value::Unit
526 },
527
528 "dialog_new"
530 | "建对话模型"
531 | "対話モデル作成"
532 | "대화모델생성"
533 | "สร้างโมเดลสนทนา"
534 | "dialog_load_model"
535 | "对话载模"
536 | "対話モデル読込"
537 | "대화모델불러오기"
538 | "โหลดโมเดลสนทนา"
539 | "dialog_train"
540 | "对话训练"
541 | "対話訓練"
542 | "대화훈련"
543 | "ฝึกสนทนา"
544 | "dialog_load"
545 | "对话载入"
546 | "対話読込"
547 | "대화불러오기"
548 | "โหลดชุดสนทนา" | "مدل_گفتگوی_جدید" | "نموذج_حوار_جديد" | "מודל_דיאלוג_חדש" | "نیا_مکالمہ_ماڈل" => Value::Number(-1.0),
549 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" | "بگو" | "قل" | "אמור" | "کہو" => {
550 Value::Str(String::new())
551 },
552 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" | "ذخیره_مدل_گفتگو" | "احفظ_نموذج_الحوار" | "שמור_מודל_דיאלוג" | "مکالمہ_ماڈل_محفوظ" => {
553 Value::Bool(false)
554 },
555 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" | "یادگیری_گفتگو" | "تعلّم_الحوار" | "למד_דיאלוג" | "مکالمہ_سیکھو" => {
556 Value::Unit
557 },
558
559 "net_connect"
561 | "联网"
562 | "ネット接続"
563 | "네트연결"
564 | "เชื่อมเน็ต"
565 | "net_listen"
566 | "监听"
567 | "待機"
568 | "리슨"
569 | "รอรับ"
570 | "net_send"
571 | "发送"
572 | "送信"
573 | "전송"
574 | "ส่ง" | "اتصال_شبکه" | "اتصل_بالشبكة" | "התחבר_לרשת" | "نیٹ_کنیکٹ" => Value::Number(-1.0),
575 "net_recv"
576 | "接收"
577 | "受信"
578 | "수신"
579 | "รับ"
580 | "net_status"
581 | "连接状态"
582 | "接続状態"
583 | "연결상태"
584 | "สถานะการเชื่อม" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" => Value::Str(String::new()),
585 "net_discover" | "发现" | "探索" | "검색" | "ค้นหาเครือข่าย" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" => {
586 Value::List(Vec::new().into())
587 },
588 "net_close"
589 | "断开"
590 | "切断"
591 | "연결종료"
592 | "ตัดเชื่อม"
593 | "net_test"
594 | "测连接"
595 | "接続テスト"
596 | "연결테스트"
597 | "ทดสอบเน็ต" | "بستن_شبکه" | "أغلق_الشبكة" | "סגור_רשת" | "نیٹ_بند" => Value::Number(0.0),
598
599 _ => Value::Unit,
601 })
602}
603
604#[derive(Debug)]
607pub(crate) enum EvalErr {
608 Runtime(String),
609 Return(Value),
610 #[allow(dead_code)] Break,
612}
613
614impl From<String> for EvalErr {
615 fn from(s: String) -> Self {
616 EvalErr::Runtime(s)
617 }
618}
619
620type EvalResult = Result<Value, EvalErr>;
621
622#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
624fn base32_encode(data: &[u8]) -> String {
625 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
626 let mut out = String::new();
627 let mut buffer: u32 = 0;
628 let mut bits = 0u32;
629 for &b in data {
630 buffer = (buffer << 8) | b as u32;
631 bits += 8;
632 while bits >= 5 {
633 bits -= 5;
634 out.push(ALPHABET[((buffer >> bits) & 0x1f) as usize] as char);
635 }
636 }
637 if bits > 0 {
638 out.push(ALPHABET[((buffer << (5 - bits)) & 0x1f) as usize] as char);
639 }
640 out
641}
642
643#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
645fn base32_decode(s: &str) -> Option<Vec<u8>> {
646 let mut buffer: u32 = 0;
647 let mut bits = 0u32;
648 let mut out = Vec::new();
649 for c in s.chars() {
650 if c == '=' || c.is_whitespace() {
651 continue;
652 }
653 let v = match c.to_ascii_uppercase() {
654 'A'..='Z' => c.to_ascii_uppercase() as u32 - 'A' as u32,
655 '2'..='7' => c as u32 - '2' as u32 + 26,
656 _ => return None,
657 };
658 buffer = (buffer << 5) | v;
659 bits += 5;
660 if bits >= 8 {
661 bits -= 8;
662 out.push((buffer >> bits) as u8);
663 }
664 }
665 Some(out)
666}
667
668#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
670fn totp_code(secret_b32: &str, step: u64) -> Option<String> {
671 let key = base32_decode(secret_b32)?;
672 let msg = step.to_be_bytes();
673 let mac = hmac_sha1(&key, &msg);
674 let offset = (mac[19] & 0x0f) as usize;
675 let bin = ((mac[offset] as u32 & 0x7f) << 24)
676 | ((mac[offset + 1] as u32) << 16)
677 | ((mac[offset + 2] as u32) << 8)
678 | (mac[offset + 3] as u32);
679 Some(format!("{:06}", bin % 1_000_000))
680}
681
682#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
684fn totp_check(secret_b32: &str, code: &str) -> bool {
685 if code.len() != 6 || !code.bytes().all(|b| b.is_ascii_digit()) {
686 return false;
687 }
688 let now_step = (crate::runtime::now_secs() as u64) / 30;
689 for delta in [-1i64, 0, 1] {
690 let step = (now_step as i64 + delta) as u64;
691 if let Some(expected) = totp_code(secret_b32, step) {
692 if expected.as_bytes().ct_eq_str(code.as_bytes()) {
694 return true;
695 }
696 }
697 }
698 false
699}
700
701#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
703fn hmac_sha1(key: &[u8], msg: &[u8]) -> [u8; 20] {
704 use hmac::{Mac, SimpleHmac};
705 let mut mac = SimpleHmac::<sha1::Sha1>::new_from_slice(key).expect("hmac key");
706 mac.update(msg);
707 let out = mac.finalize().into_bytes();
708 let mut arr = [0u8; 20];
709 arr.copy_from_slice(&out);
710 arr
711}
712
713#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
715trait CtEqStr {
716 fn ct_eq_str(&self, other: &[u8]) -> bool;
717}
718#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
719impl CtEqStr for [u8] {
720 fn ct_eq_str(&self, other: &[u8]) -> bool {
721 if self.len() != other.len() {
722 return false;
723 }
724 let mut diff = 0u8;
725 for (a, b) in self.iter().zip(other.iter()) {
726 diff |= a ^ b;
727 }
728 diff == 0
729 }
730}
731
732fn url_decode(s: &str) -> String {
734 let bytes = s.as_bytes();
735 let mut out = Vec::with_capacity(bytes.len());
736 let mut i = 0;
737 while i < bytes.len() {
738 match bytes[i] {
739 b'+' => {
740 out.push(b' ');
741 i += 1;
742 },
743 b'%' if i + 2 < bytes.len() => {
744 let hi = (bytes[i + 1] as char).to_digit(16);
745 let lo = (bytes[i + 2] as char).to_digit(16);
746 if let (Some(h), Some(l)) = (hi, lo) {
747 out.push((h * 16 + l) as u8);
748 i += 3;
749 } else {
750 out.push(bytes[i]);
751 i += 1;
752 }
753 },
754 b => {
755 out.push(b);
756 i += 1;
757 },
758 }
759 }
760 String::from_utf8_lossy(&out).into_owned()
761}
762
763#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
765fn values_to_sql_params(args: &[Value]) -> Vec<ling_http::rusqlite::types::Value> {
766 use ling_http::rusqlite::types::Value as Sql;
767 args.iter()
768 .map(|v| match v {
769 Value::Number(n) if n.fract() == 0.0 && n.abs() < 9e15 => Sql::Integer(*n as i64),
770 Value::Number(n) => Sql::Real(*n),
771 Value::Bool(b) => Sql::Integer(*b as i64),
772 other => Sql::Text(other.to_string()),
773 })
774 .collect()
775}
776
777struct SvgWriter {
782 path: String,
783 width: f64,
784 height: f64,
785 elements: Vec<String>,
786}
787
788impl SvgWriter {
789 fn new(path: String, width: f64, height: f64) -> Self {
790 let bg = format!("<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>");
791 Self { path, width, height, elements: vec![bg] }
792 }
793
794 fn save(&self) -> std::io::Result<()> {
795 let w = self.width;
796 let h = self.height;
797 let mut out = format!(
798 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
799 <svg xmlns=\"http://www.w3.org/2000/svg\" \
800 width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
801 );
802 for elem in &self.elements {
803 out.push_str(" ");
804 out.push_str(elem);
805 out.push('\n');
806 }
807 out.push_str("</svg>\n");
808 if let Some(parent) = std::path::Path::new(&self.path).parent() {
810 if !parent.as_os_str().is_empty() {
811 let _ = std::fs::create_dir_all(parent);
812 }
813 }
814 std::fs::write(&self.path, out.as_bytes())
815 }
816}
817
818fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
819 let s = s / 100.0;
820 let l = l / 100.0;
821 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
822 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
823 let m = l - c / 2.0;
824 let (r1, g1, b1) = if h < 60.0 {
825 (c, x, 0.0)
826 } else if h < 120.0 {
827 (x, c, 0.0)
828 } else if h < 180.0 {
829 (0.0, c, x)
830 } else if h < 240.0 {
831 (0.0, x, c)
832 } else if h < 300.0 {
833 (x, 0.0, c)
834 } else {
835 (c, 0.0, x)
836 };
837 let r = ((r1 + m) * 255.0).round() as u8;
838 let g = ((g1 + m) * 255.0).round() as u8;
839 let b = ((b1 + m) * 255.0).round() as u8;
840 format!("#{r:02x}{g:02x}{b:02x}")
841}
842
843fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
846 let mut h = (x as u32)
847 .wrapping_add((y as u32).wrapping_mul(2654435769))
848 .wrapping_add(seed.wrapping_mul(1234567891));
849 h ^= h >> 16;
850 h = h.wrapping_mul(0x45d9f3b);
851 h ^= h >> 16;
852 h as f32 / u32::MAX as f32
853}
854
855fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
856 let xi = x.floor() as i32;
857 let yi = y.floor() as i32;
858 let sm = |t: f32| t * t * (3.0 - 2.0 * t);
859 let xf = sm(x - xi as f32);
860 let yf = sm(y - yi as f32);
861 let a = tex_hash(xi, yi, seed);
862 let b = tex_hash(xi + 1, yi, seed);
863 let c = tex_hash(xi, yi + 1, seed);
864 let d = tex_hash(xi + 1, yi + 1, seed);
865 a + (b - a) * xf + (c - a) * yf + (a - b - c + d) * xf * yf
866}
867
868fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
869 let mut v = 0.0f32;
870 let mut amp = 0.5f32;
871 let mut f = 1.0f32;
872 for i in 0..octaves {
873 v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
874 amp *= 0.5;
875 f *= 2.0;
876 }
877 v
878}
879
880fn tex_palette(name: &str, t: f32) -> [f32; 3] {
881 let (a, b, c, d): ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) = match name {
882 "fire" => (
883 [0.8, 0.4, 0.1],
884 [0.7, 0.3, 0.1],
885 [1.0, 0.5, 0.3],
886 [0.0, 0.5, 0.8],
887 ),
888 "ocean" => (
889 [0.1, 0.4, 0.7],
890 [0.3, 0.3, 0.4],
891 [0.8, 1.0, 0.5],
892 [0.3, 0.0, 0.6],
893 ),
894 "psychedelic" => (
895 [0.5, 0.5, 0.5],
896 [0.8, 0.8, 0.8],
897 [1.0, 1.3, 0.7],
898 [0.0, 0.15, 0.3],
899 ),
900 "neon" => (
901 [0.5, 0.5, 0.5],
902 [0.5, 0.5, 0.5],
903 [2.0, 1.0, 0.0],
904 [0.5, 0.2, 0.25],
905 ),
906 "forest" => (
907 [0.3, 0.5, 0.2],
908 [0.2, 0.3, 0.1],
909 [1.0, 0.5, 0.8],
910 [0.1, 0.3, 0.6],
911 ),
912 _ => (
913 [0.5, 0.5, 0.5],
914 [0.5, 0.5, 0.5],
915 [1.0, 1.0, 1.0],
916 [0.0, 0.333, 0.667],
917 ),
918 };
919 [0, 1, 2]
920 .map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
921}
922
923#[cfg(not(target_arch = "wasm32"))]
925fn key_char(k: minifb::Key, shift: bool) -> Option<char> {
931 use minifb::Key::*;
932 let base = match k {
933 A => 'a',
934 B => 'b',
935 C => 'c',
936 D => 'd',
937 E => 'e',
938 F => 'f',
939 G => 'g',
940 H => 'h',
941 I => 'i',
942 J => 'j',
943 K => 'k',
944 L => 'l',
945 M => 'm',
946 N => 'n',
947 O => 'o',
948 P => 'p',
949 Q => 'q',
950 R => 'r',
951 S => 's',
952 T => 't',
953 U => 'u',
954 V => 'v',
955 W => 'w',
956 X => 'x',
957 Y => 'y',
958 Z => 'z',
959 Key0 => '0',
960 Key1 => '1',
961 Key2 => '2',
962 Key3 => '3',
963 Key4 => '4',
964 Key5 => '5',
965 Key6 => '6',
966 Key7 => '7',
967 Key8 => '8',
968 Key9 => '9',
969 Space => ' ',
970 Minus => '-',
971 Equal => '=',
972 Period => '.',
973 Comma => ',',
974 Slash => '/',
975 Backslash => '\\',
976 Semicolon => ';',
977 Apostrophe => '\'',
978 LeftBracket => '[',
979 RightBracket => ']',
980 Backquote => '`',
981 _ => return None,
982 };
983 if !shift {
984 return Some(base);
985 }
986 Some(match base {
987 'a'..='z' => base.to_ascii_uppercase(),
988 '0' => ')',
989 '1' => '!',
990 '2' => '@',
991 '3' => '#',
992 '4' => '$',
993 '5' => '%',
994 '6' => '^',
995 '7' => '&',
996 '8' => '*',
997 '9' => '(',
998 '-' => '_',
999 '=' => '+',
1000 '.' => '>',
1001 ',' => '<',
1002 '/' => '?',
1003 '\\' => '|',
1004 ';' => ':',
1005 '\'' => '"',
1006 '[' => '{',
1007 ']' => '}',
1008 '`' => '~',
1009 other => other,
1010 })
1011}
1012
1013#[cfg(all(not(target_arch = "wasm32"), windows))]
1017fn vk_char(vk: i32, shift: bool) -> Option<char> {
1018 let base = match vk {
1019 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,
1034 };
1035 if !shift {
1036 return Some(base);
1037 }
1038 Some(match base {
1039 'a'..='z' => base.to_ascii_uppercase(),
1040 '0' => ')',
1041 '1' => '!',
1042 '2' => '@',
1043 '3' => '#',
1044 '4' => '$',
1045 '5' => '%',
1046 '6' => '^',
1047 '7' => '&',
1048 '8' => '*',
1049 '9' => '(',
1050 '-' => '_',
1051 '=' => '+',
1052 '.' => '>',
1053 ',' => '<',
1054 '/' => '?',
1055 '\\' => '|',
1056 ';' => ':',
1057 '\'' => '"',
1058 '[' => '{',
1059 ']' => '}',
1060 '`' => '~',
1061 other => other,
1062 })
1063}
1064
1065#[cfg(all(not(target_arch = "wasm32"), windows))]
1068const TEXT_POLL_VKS: &[i32] = &[
1069 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
1070 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x30, 0x31, 0x32, 0x33,
1071 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0xBD, 0xBB, 0xBE, 0xBC, 0xBF, 0xDC, 0xBA, 0xDE,
1072 0xDB, 0xDD, 0xC0,
1073];
1074#[cfg(all(not(target_arch = "wasm32"), windows))]
1077const VK_BACK: i32 = 0x08;
1078#[cfg(all(not(target_arch = "wasm32"), windows))]
1079const VK_SHIFT: i32 = 0x10;
1080
1081#[cfg(all(not(target_arch = "wasm32"), windows))]
1088fn os_key_down(vk: i32) -> bool {
1089 unsafe {
1090 extern "system" {
1091 fn GetAsyncKeyState(vkey: i32) -> i16;
1092 }
1093 (GetAsyncKeyState(vk) as u16 & 0x8000) != 0
1094 }
1095}
1096
1097#[cfg(all(not(target_arch = "wasm32"), windows))]
1105fn window_is_foreground(hwnd: isize) -> bool {
1106 unsafe {
1107 extern "system" {
1108 fn GetForegroundWindow() -> isize;
1109 }
1110 hwnd != 0 && GetForegroundWindow() == hwnd
1111 }
1112}
1113
1114#[cfg(all(not(target_arch = "wasm32"), windows))]
1117const KEY_REPEAT_DELAY: f64 = 0.45;
1118#[cfg(all(not(target_arch = "wasm32"), windows))]
1120const KEY_REPEAT_RATE: f64 = 0.045;
1121
1122#[cfg(all(not(target_arch = "wasm32"), windows))]
1129fn key_repeat_fire(now: f64, down: bool, was_down: bool, down_since: &mut f64, last_fire: &mut f64) -> bool {
1130 if !down {
1131 return false;
1132 }
1133 if !was_down {
1134 *down_since = now;
1135 *last_fire = now;
1136 return true;
1137 }
1138 if now - *down_since >= KEY_REPEAT_DELAY && now - *last_fire >= KEY_REPEAT_RATE {
1139 *last_fire = now;
1140 return true;
1141 }
1142 false
1143}
1144
1145#[cfg(all(not(target_arch = "wasm32"), windows))]
1149fn str_to_vk(name: &str) -> Option<i32> {
1150 Some(match name {
1151 "numpad0" | "kp0" => 0x60,
1152 "numpad1" | "kp1" => 0x61,
1153 "numpad2" | "kp2" => 0x62,
1154 "numpad3" | "kp3" => 0x63,
1155 "numpad4" | "kp4" => 0x64,
1156 "numpad5" | "kp5" => 0x65,
1157 "numpad6" | "kp6" => 0x66,
1158 "numpad7" | "kp7" => 0x67,
1159 "numpad8" | "kp8" => 0x68,
1160 "numpad9" | "kp9" => 0x69,
1161 "numpad+" | "kp+" => 0x6B,
1162 "numpad-" | "kp-" => 0x6D,
1163 "numpad*" | "kp*" => 0x6A,
1164 "numpad/" | "kp/" => 0x6F,
1165 "left" => 0x25,
1166 "up" => 0x26,
1167 "right" => 0x27,
1168 "down" => 0x28,
1169 "space" => 0x20,
1170 "enter" => 0x0D,
1171 "escape" => 0x1B,
1172 "pageup" => 0x21,
1173 "pagedown" => 0x22,
1174 "lshift" | "leftshift" => 0xA0,
1175 "rshift" | "rightshift" => 0xA1,
1176 "lctrl" | "leftctrl" => 0xA2,
1177 "rctrl" | "rightctrl" => 0xA3,
1178 "lalt" | "leftalt" => 0xA4,
1179 "ralt" | "rightalt" => 0xA5,
1180 "tab" => 0x09,
1181 "backspace" => VK_BACK,
1182 "delete" => 0x2E,
1183 "insert" => 0x2D,
1184 "home" => 0x24,
1185 "end" => 0x23,
1186 "a" => 0x41,
1187 "b" => 0x42,
1188 "c" => 0x43,
1189 "d" => 0x44,
1190 "e" => 0x45,
1191 "f" => 0x46,
1192 "g" => 0x47,
1193 "h" => 0x48,
1194 "i" => 0x49,
1195 "j" => 0x4A,
1196 "k" => 0x4B,
1197 "l" => 0x4C,
1198 "m" => 0x4D,
1199 "n" => 0x4E,
1200 "o" => 0x4F,
1201 "p" => 0x50,
1202 "q" => 0x51,
1203 "r" => 0x52,
1204 "s" => 0x53,
1205 "t" => 0x54,
1206 "u" => 0x55,
1207 "v" => 0x56,
1208 "w" => 0x57,
1209 "x" => 0x58,
1210 "y" => 0x59,
1211 "z" => 0x5A,
1212 "0" => 0x30,
1213 "1" => 0x31,
1214 "2" => 0x32,
1215 "3" => 0x33,
1216 "4" => 0x34,
1217 "5" => 0x35,
1218 "6" => 0x36,
1219 "7" => 0x37,
1220 "8" => 0x38,
1221 "9" => 0x39,
1222 _ => return None,
1223 })
1224}
1225
1226fn hex_encode(bytes: &[u8]) -> String {
1228 let mut s = String::with_capacity(bytes.len() * 2);
1229 for b in bytes {
1230 s.push_str(&format!("{b:02x}"));
1231 }
1232 s
1233}
1234
1235#[cfg(not(target_arch = "wasm32"))]
1237fn decode_blob(s: &str) -> Result<Vec<u8>, String> {
1238 use base64::Engine as _;
1239 use std::io::Read as _;
1240 let comp = base64::engine::general_purpose::STANDARD
1241 .decode(s.trim())
1242 .map_err(|e| format!("base64: {e}"))?;
1243 let mut out = Vec::new();
1244 flate2::read::ZlibDecoder::new(&comp[..])
1245 .read_to_end(&mut out)
1246 .map_err(|e| format!("inflate: {e}"))?;
1247 Ok(out)
1248}
1249
1250#[cfg(not(target_arch = "wasm32"))]
1252fn hex_decode(s: &str) -> Vec<u8> {
1253 let s = s.trim();
1254 (0..s.len() / 2)
1255 .filter_map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
1256 .collect()
1257}
1258
1259#[cfg(not(target_arch = "wasm32"))]
1261fn hex_to_32(s: &str) -> [u8; 32] {
1262 let v = hex_decode(s);
1263 let mut out = [0u8; 32];
1264 let n = v.len().min(32);
1265 out[..n].copy_from_slice(&v[..n]);
1266 out
1267}
1268
1269#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1277fn build_pdf_from_images(paths: &[String], out_path: &str) -> Result<(), Box<dyn std::error::Error>> {
1278 use std::io::Write as _;
1279
1280 struct PageImg {
1281 w: u32,
1282 h: u32,
1283 compressed: Vec<u8>,
1284 }
1285
1286 let mut pages = Vec::with_capacity(paths.len());
1287 for p in paths {
1288 let img = image::open(p)?.to_rgb8();
1289 let (w, h) = (img.width(), img.height());
1290 let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
1291 enc.write_all(img.as_raw())?;
1292 pages.push(PageImg { w, h, compressed: enc.finish()? });
1293 }
1294
1295 let n = pages.len();
1296 let page_nums: Vec<u32> = (0..n).map(|i| 3 + (i as u32) * 3).collect();
1299 let total_objs = 2 + n * 3;
1300 let mut off = vec![0usize; total_objs + 1]; let mut buf: Vec<u8> = Vec::new();
1303 buf.extend_from_slice(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
1304
1305 off[1] = buf.len();
1306 buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1307
1308 off[2] = buf.len();
1309 let kids = page_nums.iter().map(|n| format!("{n} 0 R")).collect::<Vec<_>>().join(" ");
1310 buf.extend_from_slice(format!("2 0 obj\n<< /Type /Pages /Kids [{kids}] /Count {n} >>\nendobj\n").as_bytes());
1311
1312 for (i, page) in pages.iter().enumerate() {
1313 let page_num = page_nums[i];
1314 let content_num = page_num + 1;
1315 let image_num = page_num + 2;
1316 let (w, h) = (page.w, page.h);
1317
1318 off[page_num as usize] = buf.len();
1319 buf.extend_from_slice(format!(
1320 "{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"
1321 ).as_bytes());
1322
1323 let content = format!("q {w} 0 0 {h} 0 0 cm /Im0 Do Q");
1324 off[content_num as usize] = buf.len();
1325 buf.extend_from_slice(format!(
1326 "{content_num} 0 obj\n<< /Length {} >>\nstream\n{content}\nendstream\nendobj\n",
1327 content.len()
1328 ).as_bytes());
1329
1330 off[image_num as usize] = buf.len();
1331 buf.extend_from_slice(format!(
1332 "{image_num} 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>\nstream\n",
1333 page.compressed.len()
1334 ).as_bytes());
1335 buf.extend_from_slice(&page.compressed);
1336 buf.extend_from_slice(b"\nendstream\nendobj\n");
1337 }
1338
1339 let xref_offset = buf.len();
1340 buf.extend_from_slice(format!("xref\n0 {}\n", total_objs + 1).as_bytes());
1341 buf.extend_from_slice(b"0000000000 65535 f \n");
1342 for entry in off.iter().skip(1) {
1343 buf.extend_from_slice(format!("{entry:010} 00000 n \n").as_bytes());
1344 }
1345 buf.extend_from_slice(
1346 format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF", total_objs + 1).as_bytes(),
1347 );
1348
1349 std::fs::write(out_path, buf)?;
1350 Ok(())
1351}
1352
1353fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
1354 ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
1355}
1356
1357const PERM: [u8; 512] = [
1360 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1361 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1362 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1363 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1364 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1365 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1366 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1367 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1368 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1369 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1370 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180,
1371 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
1372 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
1373 140, 161, 137, 13, 191, 230, 66, 104, 153, 199, 167, 147, 99, 179, 92,
1374 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1376 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1377 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1378 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1379 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1380 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1381 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1382 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1383 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1384 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1385 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174,
1386];
1387
1388fn fade(t: f32) -> f32 {
1389 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1390}
1391
1392fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
1393 let h = hash & 15;
1394 let u = if h < 8 { x } else { y };
1395 let v = if h < 8 { y } else { z };
1396 (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v })
1397}
1398
1399fn perlin3(x: f32, y: f32, z: f32) -> f32 {
1400 let xi = (x.floor() as i32) & 255;
1401 let yi = (y.floor() as i32) & 255;
1402 let zi = (z.floor() as i32) & 255;
1403
1404 let xf = x - x.floor();
1405 let yf = y - y.floor();
1406 let zf = z - z.floor();
1407
1408 let u = fade(xf);
1409 let v = fade(yf);
1410 let w = fade(zf);
1411
1412 let p0 = PERM[xi as usize] as usize;
1413 let p1 = PERM[((xi + 1) & 255) as usize] as usize;
1414 let pa = PERM[(p0 + yi as usize) & 255] as usize;
1415 let pb = PERM[(p0 + ((yi + 1) & 255) as usize) & 255] as usize;
1416 let pc = PERM[(p1 + yi as usize) & 255] as usize;
1417 let pd = PERM[(p1 + ((yi + 1) & 255) as usize) & 255] as usize;
1418
1419 let g000 = grad(PERM[(pa + zi as usize) & 255], xf, yf, zf);
1420 let g001 = grad(
1421 PERM[(pa + ((zi + 1) & 255) as usize) & 255],
1422 xf,
1423 yf,
1424 zf - 1.0,
1425 );
1426 let g010 = grad(PERM[(pb + zi as usize) & 255], xf, yf - 1.0, zf);
1427 let g011 = grad(
1428 PERM[(pb + ((zi + 1) & 255) as usize) & 255],
1429 xf,
1430 yf - 1.0,
1431 zf - 1.0,
1432 );
1433 let g100 = grad(PERM[(pc + zi as usize) & 255], xf - 1.0, yf, zf);
1434 let g101 = grad(
1435 PERM[(pc + ((zi + 1) & 255) as usize) & 255],
1436 xf - 1.0,
1437 yf,
1438 zf - 1.0,
1439 );
1440 let g110 = grad(PERM[(pd + zi as usize) & 255], xf - 1.0, yf - 1.0, zf);
1441 let g111 = grad(
1442 PERM[(pd + ((zi + 1) & 255) as usize) & 255],
1443 xf - 1.0,
1444 yf - 1.0,
1445 zf - 1.0,
1446 );
1447
1448 let l00 = g000 + u * (g100 - g000);
1449 let l01 = g001 + u * (g101 - g001);
1450 let l10 = g010 + u * (g110 - g010);
1451 let l11 = g011 + u * (g111 - g011);
1452
1453 let l0 = l00 + v * (l10 - l00);
1454 let l1 = l01 + v * (l11 - l01);
1455
1456 l0 + w * (l1 - l0)
1457}
1458
1459fn fast_rand_f64(state: &mut u64) -> f64 {
1460 *state = state
1461 .wrapping_mul(6364136223846793005)
1462 .wrapping_add(1442695040888963407);
1463 ((*state >> 32) as u32) as f64 / 4294967296.0
1464}
1465
1466#[inline]
1470fn put_px(buf: &mut [u32], idx: usize, color: u32, blend: u8) {
1471 if idx >= buf.len() {
1472 return;
1473 }
1474 if blend == 0 {
1475 buf[idx] = color;
1476 } else {
1477 let old = buf[idx];
1478 let r = (((old >> 16) & 255) + ((color >> 16) & 255)).min(255);
1479 let g = (((old >> 8) & 255) + ((color >> 8) & 255)).min(255);
1480 let b = ((old & 255) + (color & 255)).min(255);
1481 buf[idx] = (r << 16) | (g << 8) | b;
1482 }
1483}
1484
1485#[inline]
1487fn rgb(r: f64, g: f64, b: f64) -> u32 {
1488 let r = (r as i64).clamp(0, 255) as u32;
1489 let g = (g as i64).clamp(0, 255) as u32;
1490 let b = (b as i64).clamp(0, 255) as u32;
1491 (r << 16) | (g << 8) | b
1492}
1493
1494#[allow(clippy::too_many_arguments)]
1495fn draw_circle_outline(
1496 buf: &mut [u32],
1497 w: i32,
1498 h: i32,
1499 cx: i32,
1500 cy: i32,
1501 r: i32,
1502 color: u32,
1503 blend: u8,
1504) {
1505 let r = r.clamp(0, 20000); if r == 0 {
1507 return;
1508 }
1509 let mut x = 0;
1510 let mut y = r;
1511 let mut d = 3 - 2 * r;
1512 while x <= y {
1513 plot_circle_points(buf, w, h, cx, cy, x, y, color, blend);
1514 if d < 0 {
1515 d += 4 * x + 6;
1516 } else {
1517 d += 4 * (x - y) + 10;
1518 y -= 1;
1519 }
1520 x += 1;
1521 }
1522}
1523
1524#[allow(clippy::too_many_arguments)]
1525fn plot_circle_points(
1526 buf: &mut [u32],
1527 w: i32,
1528 h: i32,
1529 cx: i32,
1530 cy: i32,
1531 x: i32,
1532 y: i32,
1533 color: u32,
1534 blend: u8,
1535) {
1536 let points = [
1537 (cx + x, cy + y),
1538 (cx - x, cy + y),
1539 (cx + x, cy - y),
1540 (cx - x, cy - y),
1541 (cx + y, cy + x),
1542 (cx - y, cy + x),
1543 (cx + y, cy - x),
1544 (cx - y, cy - x),
1545 ];
1546 for &(px, py) in &points {
1547 if px >= 0 && px < w && py >= 0 && py < h {
1548 put_px(buf, (py * w + px) as usize, color, blend);
1549 }
1550 }
1551}
1552
1553#[allow(clippy::too_many_arguments)]
1554fn draw_circle_filled(
1555 buf: &mut [u32],
1556 w: i32,
1557 h: i32,
1558 cx: i32,
1559 cy: i32,
1560 r: i32,
1561 color: u32,
1562 blend: u8,
1563) {
1564 if r <= 0 {
1565 return;
1566 }
1567 for dy in -r..=r {
1568 let dx_max = ((r * r - dy * dy) as f64).sqrt() as i32;
1569 let py = cy + dy;
1570 if py < 0 || py >= h {
1571 continue;
1572 }
1573 for dx in -dx_max..=dx_max {
1574 let px = cx + dx;
1575 if px >= 0 && px < w {
1576 put_px(buf, (py * w + px) as usize, color, blend);
1577 }
1578 }
1579 }
1580}
1581
1582#[cfg(test)]
1583mod draw_tests {
1584 use super::*;
1585
1586 #[test]
1587 fn filled_circle_actually_writes_pixels() {
1588 let mut buf = vec![0u32; 100 * 100];
1589 draw_circle_filled(&mut buf, 100, 100, 50, 50, 10, 0xFF00FF, 0);
1590 assert_eq!(buf[50 * 100 + 50], 0xFF00FF, "centre pixel must be filled");
1591 assert_eq!(buf[0], 0, "far corner must stay clear");
1592 let n = buf.iter().filter(|&&p| p != 0).count();
1593 assert!(n > 200 && n < 500, "r=10 disc area ≈ 314, got {n}");
1594 }
1595
1596 #[test]
1597 fn circle_outline_writes_a_ring() {
1598 let mut buf = vec![0u32; 100 * 100];
1599 draw_circle_outline(&mut buf, 100, 100, 50, 50, 20, 0x00FF00, 0);
1600 assert_eq!(buf[50 * 100 + 50], 0, "outline must NOT fill the centre");
1601 assert!(
1602 buf.iter().any(|&p| p == 0x00FF00),
1603 "outline must draw a ring"
1604 );
1605 }
1606
1607 #[test]
1608 fn additive_blend_accumulates_channels() {
1609 let mut buf = vec![0x202020u32; 1];
1610 put_px(&mut buf, 0, 0x404040, 1);
1611 assert_eq!(buf[0], 0x606060);
1612 }
1613}
1614
1615#[derive(Clone, Copy)]
1621pub struct UiTheme {
1622 pub primary: u32,
1623 pub accent: u32,
1624 pub track: u32,
1625 pub warn: u32,
1626 pub text: u32,
1627 pub bg: u32,
1628}
1629
1630impl Default for UiTheme {
1631 fn default() -> Self {
1632 Self {
1633 primary: 0x00D2FF, accent: 0x28FFB4, track: 0x2C3E64, warn: 0xFF5A5A, text: 0xBEEBFF, bg: 0x0A1018, }
1640 }
1641}
1642
1643#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
1644pub struct Interpreter {
1645 globals: HashMap<String, Expr>,
1646 global_seed: Env,
1649 functions: FxHashMap<String, Rc<FnDef>>,
1650 pub(crate) structs: HashMap<String, Vec<String>>,
1652 enum_variants: HashMap<String, (String, usize)>,
1654 _modules: HashMap<String, Vec<FnDef>>,
1655 gfx: RefCell<GfxState>,
1656 svg: RefCell<Option<SvgWriter>>,
1657 pub source_dir: Option<std::path::PathBuf>,
1659 loaded_files: std::collections::HashSet<String>,
1661 #[cfg(not(target_arch = "wasm32"))]
1663 audio: Option<AudioEngine>,
1664 #[cfg(not(target_arch = "wasm32"))]
1665 fft: RefCell<FftAnalyzer>,
1666 fft_bands_cache: RefCell<Vec<f32>>,
1667 start_time_secs: f64,
1670 frame_num: u64,
1672 #[cfg(target_arch = "wasm32")]
1674 wasm_target_fps: f64,
1675 #[cfg(target_arch = "wasm32")]
1677 wasm_next_present_ms: f64,
1678 rand_state: u64,
1680 #[cfg(not(target_arch = "wasm32"))]
1682 mic: Option<ling_mic::MicInput>,
1683 #[cfg(not(target_arch = "wasm32"))]
1685 crypto_ids: Vec<ling_crypto::KnotIdentity>,
1686 #[cfg(not(target_arch = "wasm32"))]
1688 ed25519_ids: Vec<ling_crypto::Ed25519Keypair>,
1689 #[cfg(not(target_arch = "wasm32"))]
1691 mldsa_ids: Vec<ling_crypto::MlDsa65Keypair>,
1692 text_buffer: String,
1694 record_n: u32,
1696 #[cfg(not(target_arch = "wasm32"))]
1698 mic_buffer: Vec<f32>,
1699 #[cfg(not(target_arch = "wasm32"))]
1701 fonts: Vec<ling_graphics::VectorFont>,
1702 images: Vec<image::RgbaImage>,
1705 ui_theme: UiTheme,
1707 mouse_was_down: bool,
1709 #[cfg(not(target_arch = "wasm32"))]
1711 music: Option<ling_music::MusicEngine>,
1712 #[cfg(not(target_arch = "wasm32"))]
1713 music_init: bool,
1714 tracks: Vec<ling_music::DecodedAudio>,
1716 lyrics: Vec<ling_music::Lyrics>,
1718 midis: Vec<ling_music::MidiSong>,
1720 soft_bodies: Vec<ling_physics::soft::SoftBody>,
1722 rigid_world: ling_physics::rigid::PhysicsWorld,
1724 liquids: Vec<ling_physics::liquid::LiquidGrid>,
1726 meshes: Vec<crate::gfx::shapes::ColorMesh>,
1727 gltf_models: std::cell::RefCell<Vec<ling_physics::gltf::GltfModel>>,
1729 dialog: Option<ling_game::dialog::Dialog>,
1731 dialog_colors: [u32; 4],
1733 frames: Vec<String>,
1735 error_trace: Option<Vec<String>>,
1738 #[cfg(not(target_arch = "wasm32"))]
1742 input: RefCell<Option<InputState>>,
1743 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1748 http_routes: Vec<(String, String, Value)>,
1749 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1753 db: Option<ling_http::rusqlite::Connection>,
1754 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1757 http_static_dirs: Vec<(String, String)>,
1758 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1762 async_jobs: web::AsyncJobs,
1763 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1767 oauth_jobs: web::OAuthJobs,
1768}
1769
1770#[cfg(not(target_arch = "wasm32"))]
1772struct InputState {
1773 sensorium: ling_input::Sensorium,
1774 backend: ling_input::backend::GilrsBackend,
1775}
1776
1777impl Default for Interpreter {
1778 fn default() -> Self {
1779 Self::new()
1780 }
1781}
1782
1783impl Interpreter {
1784 pub fn new() -> Self {
1785 #[cfg(not(target_arch = "wasm32"))]
1786 let audio = AudioEngine::new()
1787 .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
1788 .ok();
1789 Self {
1790 globals: HashMap::new(),
1791 global_seed: new_env(),
1792 functions: FxHashMap::default(),
1793 structs: HashMap::new(),
1794 enum_variants: HashMap::new(),
1795 _modules: HashMap::new(),
1796 gfx: RefCell::new(GfxState::new()),
1797 svg: RefCell::new(None),
1798 source_dir: None,
1799 loaded_files: std::collections::HashSet::new(),
1800 #[cfg(not(target_arch = "wasm32"))]
1801 audio,
1802 #[cfg(not(target_arch = "wasm32"))]
1803 fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
1804 fft_bands_cache: RefCell::new(vec![]),
1805 start_time_secs: crate::runtime::now_secs(),
1806 frame_num: 0,
1807 #[cfg(target_arch = "wasm32")]
1808 wasm_target_fps: 60.0,
1809 #[cfg(target_arch = "wasm32")]
1810 wasm_next_present_ms: 0.0,
1811 rand_state: 0x123456789ABCDEF,
1812 #[cfg(not(target_arch = "wasm32"))]
1813 mic: None,
1814 #[cfg(not(target_arch = "wasm32"))]
1815 crypto_ids: Vec::new(),
1816 #[cfg(not(target_arch = "wasm32"))]
1817 ed25519_ids: Vec::new(),
1818 #[cfg(not(target_arch = "wasm32"))]
1819 mldsa_ids: Vec::new(),
1820 text_buffer: String::new(),
1821 record_n: 0,
1822 #[cfg(not(target_arch = "wasm32"))]
1823 mic_buffer: Vec::new(),
1824 #[cfg(not(target_arch = "wasm32"))]
1825 fonts: Vec::new(),
1826 images: Vec::new(),
1827 ui_theme: UiTheme::default(),
1828 mouse_was_down: false,
1829 #[cfg(not(target_arch = "wasm32"))]
1830 music: None,
1831 #[cfg(not(target_arch = "wasm32"))]
1832 music_init: false,
1833 tracks: Vec::new(),
1834 lyrics: Vec::new(),
1835 midis: Vec::new(),
1836 soft_bodies: Vec::new(),
1837 rigid_world: ling_physics::rigid::PhysicsWorld::new(),
1838 liquids: Vec::new(),
1839 meshes: Vec::new(),
1840 gltf_models: std::cell::RefCell::new(Vec::new()),
1841 dialog: None,
1842 dialog_colors: [0xE6F2FF, 0xFFD24A, 0x4AD2FF, 0x6CFF8C], frames: Vec::new(),
1844 error_trace: None,
1845 #[cfg(not(target_arch = "wasm32"))]
1846 input: RefCell::new(None),
1847 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1848 http_routes: Vec::new(),
1849 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1850 db: None,
1851 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1852 http_static_dirs: Vec::new(),
1853 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1854 async_jobs: web::AsyncJobs::new(),
1855 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1856 oauth_jobs: web::OAuthJobs::new(),
1857 }
1858 }
1859
1860 #[cfg(not(target_arch = "wasm32"))]
1864 fn pad_poll(&self) -> usize {
1865 let mut slot = self.input.borrow_mut();
1866 if slot.is_none() {
1867 match ling_input::backend::GilrsBackend::new() {
1868 Ok(backend) => {
1869 *slot = Some(InputState { sensorium: ling_input::Sensorium::new(4), backend });
1870 },
1871 Err(_) => return 0,
1872 }
1873 }
1874 let st = slot.as_mut().unwrap();
1875 st.sensorium.begin_frame();
1876 st.sensorium.pump(&mut st.backend);
1877 st.sensorium.update(1.0 / 60.0);
1878 st.sensorium.devices.count()
1879 }
1880
1881 #[cfg(not(target_arch = "wasm32"))]
1884 fn with_pad<T>(&self, slot: usize, default: T, f: impl FnOnce(&ling_input::Gamepad) -> T) -> T {
1885 let inp = self.input.borrow();
1886 match inp.as_ref().and_then(|s| s.sensorium.player(slot)) {
1887 Some(p) => f(p),
1888 None => default,
1889 }
1890 }
1891
1892 pub fn take_error_trace(&mut self) -> Vec<String> {
1895 self.error_trace.take().unwrap_or_default()
1896 }
1897
1898 #[cfg(target_arch = "wasm32")]
1899 fn wasm_pace_frame(&mut self) {
1900 let fps = self.wasm_target_fps.max(1.0);
1901 let frame_ms = 1000.0 / fps;
1902 let now = js_sys::Date::now();
1903 if self.wasm_next_present_ms <= 0.0 {
1904 self.wasm_next_present_ms = now + frame_ms;
1905 return;
1906 }
1907
1908 let wait_ms = (self.wasm_next_present_ms - now).floor() as i32;
1909 if wait_ms > 0 {
1910 wasm_sleep_ms(wait_ms);
1911 }
1912
1913 let after = js_sys::Date::now();
1914 if after > self.wasm_next_present_ms + frame_ms * 3.0 {
1915 self.wasm_next_present_ms = after + frame_ms;
1916 } else {
1917 self.wasm_next_present_ms += frame_ms;
1918 }
1919 }
1920
1921 fn framed<T, F>(&mut self, name: &str, body: F) -> Result<T, EvalErr>
1924 where
1925 F: FnOnce(&mut Self) -> Result<T, EvalErr>,
1926 {
1927 self.frames.push(name.to_string());
1928 let result = body(self);
1929 if matches!(result, Err(EvalErr::Runtime(_))) && self.error_trace.is_none() {
1930 self.error_trace = Some(self.frames.clone());
1931 }
1932 self.frames.pop();
1933 result
1934 }
1935
1936 #[cfg(not(target_arch = "wasm32"))]
1940 fn render_dialog(&mut self, x: f32, y: f32, w: f32, h: f32, font: i64, t: f32) {
1941 let (runs, typing) = match &self.dialog {
1942 Some(d) if !d.is_closed() => {
1943 let runs: Vec<(String, usize, bool)> = d
1944 .visible_runs()
1945 .into_iter()
1946 .map(|r| (r.text, r.role.index(), r.newline_before))
1947 .collect();
1948 (runs, d.is_typing())
1949 },
1950 _ => return,
1951 };
1952 let colors = self.dialog_colors;
1953 let b = 12.0;
1955 let corners: Vec<[f32; 2]> = vec![
1956 [x + b, y],
1957 [x + w - b, y],
1958 [x + w, y + b],
1959 [x + w, y + h - b],
1960 [x + w - b, y + h],
1961 [x + b, y + h],
1962 [x, y + h - b],
1963 [x, y + b],
1964 [x + b, y],
1965 ];
1966 {
1967 let mut gfx = self.gfx.borrow_mut();
1968 let (bw, bh) = (gfx.width, gfx.height);
1969 crate::gfx::raster::fill_contours_aa(
1970 &mut gfx.buffer,
1971 bw,
1972 bh,
1973 0x0A1018,
1974 false,
1975 std::slice::from_ref(&corners),
1976 );
1977 for seg in corners.windows(2) {
1978 crate::gfx::raster::draw_line_aa(
1979 &mut gfx.buffer,
1980 bw,
1981 bh,
1982 0x00D2FF,
1983 false,
1984 seg[0][0],
1985 seg[0][1],
1986 seg[1][0],
1987 seg[1][1],
1988 );
1989 }
1990 }
1991 let px = 22.0f32;
1993 let pad = 20.0f32;
1994 let line_h = px * 1.45;
1995 let mut cx = x + pad;
1996 let mut cy = y + pad;
1997 let use_font = font >= 0 && (font as usize) < self.fonts.len();
1998 for (text, role, nl) in &runs {
1999 if *nl {
2000 cx = x + pad;
2001 cy += line_h;
2002 }
2003 for word in text.split_inclusive(' ') {
2004 let wpx = if use_font {
2005 self.fonts[font as usize].measure(word, px)
2006 } else {
2007 ling_ui::holo::text_width(word, px * 0.6, px * 0.24)
2008 };
2009 if cx + wpx > x + w - pad && cx > x + pad + 1.0 {
2010 cx = x + pad;
2011 cy += line_h;
2012 }
2013 if cy + line_h > y + h {
2014 break;
2015 }
2016 let col = colors[(*role).min(3)];
2017 if use_font {
2018 let glyphs = self.font_layout_2d_glyphs(font as usize, cx, cy, px, word);
2019 let mut gfx = self.gfx.borrow_mut();
2020 let (bw, bh, add) = (gfx.width, gfx.height, gfx.blend == 1);
2021 for contours in &glyphs {
2022 crate::gfx::raster::fill_contours_aa(
2023 &mut gfx.buffer,
2024 bw,
2025 bh,
2026 col,
2027 add,
2028 contours,
2029 );
2030 }
2031 } else {
2032 let segs = ling_ui::holo::text_lines(word, cx, cy, px * 0.6, px, px * 0.24);
2033 let mut gfx = self.gfx.borrow_mut();
2034 let (bw, bh) = (gfx.width, gfx.height);
2035 for s in segs {
2036 draw_line(&mut gfx.buffer, bw, bh, col, s[0], s[1], s[2], s[3]);
2037 }
2038 }
2039 cx += wpx;
2040 }
2041 }
2042 if !typing && (t * 3.0).sin() > 0.0 {
2044 let ax = x + w - 26.0;
2045 let ay = y + h - 22.0;
2046 let mut gfx = self.gfx.borrow_mut();
2047 let (bw, bh) = (gfx.width, gfx.height);
2048 crate::gfx::raster::fill_contours_aa(
2049 &mut gfx.buffer,
2050 bw,
2051 bh,
2052 0x00D2FF,
2053 false,
2054 std::slice::from_ref(&vec![
2055 [ax - 7.0, ay],
2056 [ax + 7.0, ay],
2057 [ax, ay + 9.0],
2058 [ax - 7.0, ay],
2059 ]),
2060 );
2061 }
2062 }
2063
2064 #[cfg(not(target_arch = "wasm32"))]
2067 fn ensure_music(&mut self) -> bool {
2068 if self.music.is_some() {
2069 return true;
2070 }
2071 if self.music_init {
2072 return false;
2073 }
2074 self.music_init = true;
2075 match ling_music::MusicEngine::new() {
2076 Ok(m) => {
2077 self.music = Some(m);
2078 true
2079 },
2080 Err(e) => {
2081 eprintln!("music engine init failed (no music playback): {e}");
2082 false
2083 },
2084 }
2085 }
2086
2087 #[cfg(target_arch = "wasm32")]
2088 fn wasm_resolve_source_path(&self, path: &str) -> String {
2089 let p = path.trim();
2090 if p.is_empty() {
2091 return String::new();
2092 }
2093 if p.contains("://") || p.starts_with('/') || p.starts_with("./") || p.starts_with("../") {
2094 return p.to_string();
2095 }
2096 if let Some(d) = &self.source_dir {
2097 let base = d.to_string_lossy().replace('\\', "/");
2098 if !base.is_empty() {
2099 return format!(
2100 "{}/{}",
2101 base.trim_end_matches('/'),
2102 p.trim_start_matches("./")
2103 );
2104 }
2105 }
2106 p.to_string()
2107 }
2108
2109 #[cfg(target_arch = "wasm32")]
2110 fn wasm_music_builtin(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, EvalErr> {
2111 match name {
2112 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" | "charger_musique" | "musik_laden" | "загрузить_музыку" =>
2114 {
2115 let path = self.arg_str(args, 0, "");
2116 let resolved = self.wasm_resolve_source_path(&path);
2117 match wasm_fetch_bytes(&resolved)
2118 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
2119 {
2120 Ok(t) => {
2121 let id = self.tracks.len();
2122 self.tracks.push(t);
2123 return Ok(Some(Value::Number(id as f64)));
2124 },
2125 Err(e) => {
2126 eprintln!("music_load failed ({path}): {e}");
2127 return Ok(Some(Value::Number(-1.0)));
2128 },
2129 }
2130 },
2131 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" | "durée_musique" | "musik_dauer" | "длительность_музыки" =>
2132 {
2133 let id = self.arg_num(args, 0, 0.0)? as i64;
2134 let d = self
2135 .tracks
2136 .get(id as usize)
2137 .map(|t| t.duration)
2138 .unwrap_or(0.0);
2139 return Ok(Some(Value::Number(d as f64)));
2140 },
2141 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" | "ضربان_در_دقیقه" | "نبضات_بالدقيقة" | "פעימות_לדקה" | "بی_پی_ایم" | "bpm_musique" | "musik_bpm" | "музыка_bpm" =>
2142 {
2143 let id = self.arg_num(args, 0, 0.0)? as i64;
2144 let b = self
2145 .tracks
2146 .get(id as usize)
2147 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
2148 .unwrap_or(0.0);
2149 return Ok(Some(Value::Number(b as f64)));
2150 },
2151 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" | "tonalité_musique" | "musik_tonart" | "тональность_музыки" => {
2152 let id = self.arg_num(args, 0, 0.0)? as i64;
2153 let k = self
2154 .tracks
2155 .get(id as usize)
2156 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
2157 .unwrap_or_default();
2158 return Ok(Some(Value::Str(k)));
2159 },
2160 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" | "attaques_musique" | "musik_einsätze" | "атаки_музыки" =>
2161 {
2162 let id = self.arg_num(args, 0, 0.0)? as i64;
2163 let v = self
2164 .tracks
2165 .get(id as usize)
2166 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
2167 .unwrap_or_default();
2168 return Ok(Some(Value::List(
2169 v.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2170 )));
2171 },
2172 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" | "شبکه_ضرب" | "شبكة_الإيقاع" | "רשת_פעימות" | "بیٹ_گرڈ" | "grille_temps_musique" | "musik_taktraster" | "сетка_ритма_музыки" =>
2173 {
2174 let id = self.arg_num(args, 0, 0.0)? as i64;
2175 let beats = self
2176 .tracks
2177 .get(id as usize)
2178 .map(|t| {
2179 let b = ling_music::analysis::bpm(&t.mono, t.rate);
2180 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
2181 })
2182 .unwrap_or_default();
2183 return Ok(Some(Value::List(
2184 beats.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2185 )));
2186 },
2187 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" | "بارگذاری_متن_ترانه" | "تحميل_كلمات_الأغنية" | "טעינת_מילות_שיר" | "گیت_متن_لوڈ" | "lrc_musique" | "musik_lrc" | "lrc_музыки" =>
2188 {
2189 let path = self.arg_str(args, 0, "");
2190 let resolved = self.wasm_resolve_source_path(&path);
2191 match wasm_fetch_text(&resolved) {
2192 Ok(text) => {
2193 let id = self.lyrics.len();
2194 self.lyrics.push(ling_music::Lyrics::parse(&text));
2195 return Ok(Some(Value::Number(id as f64)));
2196 },
2197 Err(e) => {
2198 eprintln!("music_lrc failed ({path}): {e}");
2199 return Ok(Some(Value::Number(-1.0)));
2200 },
2201 }
2202 },
2203 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" | "متن_ترانه_فعلی" | "كلمات_الأغنية_الحالية" | "מילות_שיר_נוכחיות" | "موجودہ_گیت_متن" | "paroles_musique" | "musik_liedtext" | "текст_песни" =>
2204 {
2205 let id = self.arg_num(args, 0, 0.0)? as i64;
2206 let t = self.arg_num(args, 1, 0.0)? as f32;
2207 let line = self
2208 .lyrics
2209 .get(id as usize)
2210 .map(|l| l.line_at(t).to_string())
2211 .unwrap_or_default();
2212 return Ok(Some(Value::Str(line)));
2213 },
2214 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" | "بارگذاری_MIDI" | "تحميل_MIDI" | "טעינת_MIDI" | "MIDI_لوڈ" | "charger_midi_musique" | "musik_midi_laden" | "загрузить_midi_музыки" =>
2215 {
2216 let path = self.arg_str(args, 0, "");
2217 let resolved = self.wasm_resolve_source_path(&path);
2218 match wasm_fetch_bytes(&resolved).and_then(|bytes| {
2219 ling_music::midi::from_bytes(&bytes).map_err(|e| e.to_string())
2220 }) {
2221 Ok(m) => {
2222 let id = self.midis.len();
2223 self.midis.push(m);
2224 return Ok(Some(Value::Number(id as f64)));
2225 },
2226 Err(e) => {
2227 eprintln!("music_midi_load failed ({path}): {e}");
2228 return Ok(Some(Value::Number(-1.0)));
2229 },
2230 }
2231 },
2232 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" | "تعداد_MIDI" | "عدد_MIDI" | "מספר_MIDI" | "MIDI_تعداد" | "nombre_midi_musique" | "musik_midi_anzahl" | "число_midi_музыки" =>
2233 {
2234 let id = self.arg_num(args, 0, 0.0)? as i64;
2235 let n = self
2236 .midis
2237 .get(id as usize)
2238 .map(|m| m.notes.len())
2239 .unwrap_or(0);
2240 return Ok(Some(Value::Number(n as f64)));
2241 },
2242 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" | "نتهای_MIDI" | "نغمات_MIDI" | "תווי_MIDI" | "MIDI_نوٹس" | "notes_midi_musique" | "musik_midi_noten" | "ноты_midi_музыки" =>
2243 {
2244 let id = self.arg_num(args, 0, 0.0)? as i64;
2245 let mut out = Vec::new();
2246 if let Some(m) = self.midis.get(id as usize) {
2247 for n in &m.notes {
2248 out.push(Value::Number(n.time as f64));
2249 out.push(Value::Number(n.midi as f64));
2250 }
2251 }
2252 return Ok(Some(Value::List(out.into())));
2253 },
2254 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" | "میلههای_MIDI" | "أعمدة_MIDI" | "עמודות_MIDI" | "MIDI_بارز" | "mesures_midi_musique" | "musik_midi_takte" | "такты_midi_музыки" =>
2255 {
2256 let id = self.arg_num(args, 0, 0.0)? as i64;
2257 let mut out = Vec::new();
2258 if let Some(m) = self.midis.get(id as usize) {
2259 for n in &m.notes {
2260 out.push(Value::Number(n.time as f64));
2261 out.push(Value::Number(n.midi as f64));
2262 out.push(Value::Number(n.dur as f64));
2263 }
2264 }
2265 return Ok(Some(Value::List(out.into())));
2266 },
2267 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" | "داوری_ضرب" | "حكم_الإيقاع" | "שיפוט_קצב" | "بیٹ_فیصلہ" | "juger_musique" | "musik_bewerten" | "оценить_музыку" =>
2268 {
2269 let delta_ms = self.arg_num(args, 0, 9999.0)? as f32;
2270 return Ok(Some(Value::Number(
2271 ling_music::Grade::judge(delta_ms).index() as f64,
2272 )));
2273 },
2274 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" | "نام_رتبه" | "اسم_التقييم" | "שם_דירוג" | "گریڈ_نام" | "nom_grade_musique" | "musik_bewertungsname" | "имя_оценки_музыки" =>
2275 {
2276 let idx = self.arg_num(args, 0, 4.0)? as i32;
2277 return Ok(Some(Value::Str(
2278 ling_music::Grade::from_index(idx).name().to_string(),
2279 )));
2280 },
2281 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" | "نام_نت" | "اسم_النغمة" | "שם_תו" | "نوٹ_نام" | "nom_note_musique" | "musik_notenname" | "имя_ноты_музыки" =>
2282 {
2283 let hz = self.arg_num(args, 0, 0.0)? as f32;
2284 return Ok(Some(Value::Str(ling_music::note::hz_to_name(hz))));
2285 },
2286 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" | "فرکانس_نت" | "تردد_النغمة" | "תדר_תו" | "نوٹ_ہرٹز" | "hz_musique" | "musik_hz" | "музыка_гц" =>
2287 {
2288 let midi = match args.get(0) {
2289 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(69),
2290 Some(Value::Number(n)) => *n as i32,
2291 _ => 69,
2292 };
2293 return Ok(Some(Value::Number(
2294 ling_music::note::midi_to_hz(midi as f32) as f64,
2295 )));
2296 },
2297 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" | "امتیاز_زیروبمی" | "درجة_طبقة_الصوت" | "ציון_גובה_צליל" | "پچ_اسکور" | "score_hauteur_musique" | "musik_tonhöhen_punktzahl" | "счёт_высоты_тона" =>
2298 {
2299 let hz = self.arg_num(args, 0, 0.0)? as f32;
2300 let target = self.arg_num(args, 1, 0.0)? as f32;
2301 return Ok(Some(Value::Number(
2302 ling_music::karaoke::pitch_score(hz, target) as f64,
2303 )));
2304 },
2305
2306 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" | "jouer_musique" | "musik_abspielen" | "играть_музыку" =>
2308 {
2309 let id = self.arg_num(args, 0, 0.0)? as usize;
2310 if let Some(t) = self.tracks.get(id) {
2311 crate::gfx::audio_web::play_music(id, &t.stereo, t.channels, t.rate, 1.0);
2312 }
2313 return Ok(Some(Value::Unit));
2314 },
2315 "music_pause"
2316 | "暂停音乐"
2317 | "音楽一時停止"
2318 | "음악일시정지"
2319 | "หยุดเพลงชั่วคราว"
2320 | "music_stop"
2321 | "停止音乐"
2322 | "音楽停止"
2323 | "음악정지"
2324 | "หยุดเพลง" | "مکث_موسیقی" | "ألبث_الموسيقى" | "השהה_מוזיקה" | "موسیقی_روکو_مؤقت" | "pause_musique" | "musik_pausieren" | "пауза_музыки" => {
2325 let id = self.arg_num(args, 0, 0.0)? as usize;
2326 crate::gfx::audio_web::stop_music(id);
2327 return Ok(Some(Value::Unit));
2328 },
2329 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" | "جستجوی_موسیقی" | "ابحث_في_الموسيقى" | "חפש_במוזיקה" | "موسیقی_تلاش" | "chercher_musique" | "musik_suchen" | "перемотать_музыку" =>
2330 {
2331 return Ok(Some(Value::Unit));
2333 },
2334 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" | "موقعیت_موسیقی" | "موضع_الموسيقى" | "מיקום_מוזיקה" | "موسیقی_مقام" | "position_musique" | "musik_position" | "позиция_музыки" =>
2335 {
2336 return Ok(Some(Value::Number(
2337 crate::gfx::audio_web::current_music_position(),
2338 )));
2339 },
2340 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" | "بلندی_موسیقی" | "مستوى_الموسيقى" | "עוצמת_מוזיקה" | "موسیقی_شدت" | "volume_musique" | "musik_lautstärke" | "громкость_музыки" =>
2341 {
2342 let vol = self.arg_num(args, 0, 0.8)? as f32;
2343 crate::gfx::audio_web::set_music_volume(0, vol);
2345 return Ok(Some(Value::Unit));
2346 },
2347
2348 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" | "طیف_موسیقی" | "طيف_الموسيقى" | "ספקטרום_מוזיקה" | "میوزک_اسپیکٹرم" | "fft_musique" | "musik_fft" | "fft_музыки" =>
2350 {
2351 let id = self.arg_num(args, 0, 0.0)? as usize;
2352 let nbands = self.arg_num(args, 1, 16.0)? as usize;
2353 let pos = crate::gfx::audio_web::current_music_position() as f32;
2354 let bands = if let Some(t) = self.tracks.get(id) {
2355 ling_music::analysis::fft_bands_at_pos(&t.mono, t.rate, pos, nbands)
2356 } else {
2357 vec![0.0f32; nbands]
2358 };
2359 return Ok(Some(Value::List(
2360 bands.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2361 )));
2362 },
2363
2364 _ => {},
2365 }
2366 Ok(None)
2367 }
2368
2369 #[cfg(not(target_arch = "wasm32"))]
2373 fn font_layout_2d(
2374 &mut self,
2375 id: usize,
2376 x: f32,
2377 y: f32,
2378 px: f32,
2379 text: &str,
2380 ) -> Vec<Vec<[f32; 2]>> {
2381 let mut out = Vec::new();
2382 for g in self.font_layout_2d_glyphs(id, x, y, px, text) {
2383 out.extend(g);
2384 }
2385 out
2386 }
2387
2388 #[cfg(not(target_arch = "wasm32"))]
2391 fn font_layout_2d_glyphs(
2392 &mut self,
2393 id: usize,
2394 x: f32,
2395 y: f32,
2396 px: f32,
2397 text: &str,
2398 ) -> Vec<Vec<Vec<[f32; 2]>>> {
2399 let font = &mut self.fonts[id];
2400 let asc = font.ascent();
2401 let tol = 0.3 / px;
2402 let mut pen = 0.0f32;
2403 let mut glyphs = Vec::new();
2404 for ch in text.chars() {
2405 let go = font.glyph_outline(ch, tol);
2406 let mut contours = Vec::with_capacity(go.polylines.len());
2407 for pl in &go.polylines {
2408 let mapped: Vec<[f32; 2]> = pl
2409 .iter()
2410 .map(|p| [x + (pen + p[0]) * px, y + (asc - p[1]) * px])
2411 .collect();
2412 contours.push(mapped);
2413 }
2414 glyphs.push(contours);
2415 pen += go.advance;
2416 }
2417 glyphs
2418 }
2419
2420 pub fn register_program(&mut self, program: &Program) -> Result<(), String> {
2425 for item in &program.items {
2426 self.register_item("", item)?;
2427 }
2428 let mut env = new_env();
2429 let non_do: Vec<_> = self
2430 .globals
2431 .iter()
2432 .filter(|(_, e)| !matches!(e, Expr::Do(_)))
2433 .map(|(k, e)| (k.clone(), e.clone()))
2434 .collect();
2435 let mut pending: Vec<(String, Expr)> = Vec::new();
2436 for (k, expr) in &non_do {
2437 let mut tmp = new_env();
2438 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2439 env.insert(k.clone(), v);
2440 } else {
2441 pending.push((k.clone(), expr.clone()));
2442 }
2443 }
2444 for (k, expr) in &pending {
2445 let mut tmp = env.clone();
2446 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2447 env.insert(k.clone(), v);
2448 }
2449 }
2450 self.global_seed = env;
2451 Ok(())
2452 }
2453
2454 pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
2455 self.register_program(program)?;
2456 let entry = self
2457 .find_entry()
2458 .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
2459 let mut env = self.global_seed.clone();
2460 self.framed("start", |me| me.eval_expr(&entry, &mut env))
2461 .map(|_| ())
2462 .map_err(|e| match e {
2463 EvalErr::Runtime(s) => s,
2464 EvalErr::Return(_) => "unexpected top-level return".to_string(),
2465 EvalErr::Break => "unexpected break at top level".to_string(),
2466 })
2467 }
2468
2469 fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
2470 match item {
2471 Item::Bind(name, expr) => {
2472 let key = if ns.is_empty() {
2473 name.clone()
2474 } else {
2475 format!("{ns}::{name}")
2476 };
2477 self.globals.insert(key, expr.clone());
2478 },
2479 Item::Fn(def) => {
2480 let key = if ns.is_empty() {
2481 def.name.clone()
2482 } else {
2483 format!("{ns}::{}", def.name)
2484 };
2485 self.functions.insert(key, Rc::new(def.clone()));
2486 },
2487 Item::Mod(name, body) => {
2488 let child_ns = if ns.is_empty() {
2489 name.clone()
2490 } else {
2491 format!("{ns}::{name}")
2492 };
2493 for child in body {
2494 self.register_item(&child_ns, child)?;
2495 }
2496 },
2497 Item::TypeAlias(_, _) => {},
2498 Item::Struct(name, fields) => {
2499 self.structs.insert(name.clone(), fields.clone());
2500 if !ns.is_empty() {
2501 self.structs.insert(format!("{ns}::{name}"), fields.clone());
2502 }
2503 },
2504 Item::Enum(name, variants) => {
2505 for v in variants {
2506 self.enum_variants
2507 .insert(v.name.clone(), (name.clone(), v.arity));
2508 self.enum_variants
2509 .insert(format!("{name}::{}", v.name), (name.clone(), v.arity));
2510 if !ns.is_empty() {
2511 self.enum_variants
2512 .insert(format!("{ns}::{name}::{}", v.name), (name.clone(), v.arity));
2513 }
2514 }
2515 },
2516 Item::Use { path, alias } => {
2517 self.load_module(path, alias.as_deref(), ns)?;
2518 },
2519 }
2520 Ok(())
2521 }
2522
2523 fn load_module(
2528 &mut self,
2529 path: &str,
2530 alias: Option<&str>,
2531 parent_ns: &str,
2532 ) -> Result<(), String> {
2533 #[cfg(target_arch = "wasm32")]
2535 let (source, sub_dir) = {
2536 if self.loaded_files.contains(path) {
2538 return Ok(());
2539 }
2540 self.loaded_files.insert(path.to_string());
2541
2542 let src = crate::runtime::get_wasm_module(path)
2543 .or_else(|| crate::runtime::get_wasm_module(&format!("{}.ling", path)))
2544 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2545 (src, None::<std::path::PathBuf>)
2546 };
2547
2548 #[cfg(not(target_arch = "wasm32"))]
2550 let (source, sub_dir) = {
2551 let base_dir = self
2552 .source_dir
2553 .clone()
2554 .unwrap_or_else(|| std::path::PathBuf::from("."));
2555 let raw = std::path::Path::new(path);
2556 let candidates: Vec<std::path::PathBuf> = vec![
2557 base_dir.join(format!("{}.ling", path)),
2558 base_dir.join(format!("{}.灵", path)),
2559 base_dir.join(format!("{}.령", path)),
2560 base_dir.join(format!("{}.霊", path)),
2561 base_dir.join(format!("{}.ลิง", path)),
2562 base_dir.join(raw),
2563 std::path::PathBuf::from(format!("{}.ling", path)),
2564 std::path::PathBuf::from(path),
2565 ];
2566
2567 let resolved = candidates
2568 .into_iter()
2569 .find(|p| p.exists())
2570 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2571
2572 let canonical = resolved
2573 .canonicalize()
2574 .unwrap_or_else(|_| resolved.clone())
2575 .to_string_lossy()
2576 .to_string();
2577
2578 if self.loaded_files.contains(&canonical) {
2580 return Ok(());
2581 }
2582 self.loaded_files.insert(canonical.clone());
2583
2584 let src = std::fs::read_to_string(&resolved)
2585 .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
2586 let dir = resolved.parent().map(|p| p.to_path_buf());
2587 (src, dir)
2588 };
2589
2590 let program = crate::parser::parse(&source)
2591 .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
2592
2593 let target_ns = match (parent_ns.is_empty(), alias) {
2595 (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
2596 (_, Some(a)) => a.to_string(),
2597 (false, None) => parent_ns.to_string(),
2598 (true, None) => String::new(),
2599 };
2600
2601 let prev_dir = self.source_dir.clone();
2603 self.source_dir = sub_dir;
2604
2605 for item in &program.items {
2606 self.register_item(&target_ns, item)?;
2607 }
2608
2609 self.source_dir = prev_dir;
2610 Ok(())
2611 }
2612
2613 fn find_entry(&self) -> Option<Expr> {
2614 for key in crate::entry::ENTRY_NAMES {
2616 if let Some(e) = self.globals.get(*key) {
2617 return Some(e.clone());
2618 }
2619 }
2620 self.globals
2621 .values()
2622 .find(|e| matches!(e, Expr::Do(_)))
2623 .cloned()
2624 }
2625
2626 fn eval_expr(&mut self, expr: &Expr, env: &mut Env) -> EvalResult {
2629 match expr {
2630 Expr::Str(s) => Ok(Value::Str(s.clone())),
2631 Expr::Number(n) => Ok(Value::Number(*n)),
2632 Expr::Bool(b) => Ok(Value::Bool(*b)),
2633 Expr::Unit => Ok(Value::Unit),
2634 Expr::Array(elems) => {
2635 let vs: Vec<_> = elems
2636 .iter()
2637 .map(|e| self.eval_expr(e, env))
2638 .collect::<Result<_, _>>()?;
2639 Ok(Value::List(Rc::new(vs)))
2640 },
2641
2642 Expr::Ident(name) => self.lookup(name, env),
2643
2644 Expr::Path(segs) => {
2645 if segs.len() == 1 {
2646 return self.lookup(&segs[0], env);
2647 }
2648 Ok(Value::Str(segs.join("::")))
2649 },
2650
2651 Expr::Ref(inner) => self.eval_expr(inner, env),
2652 Expr::Await(inner) => self.eval_expr(inner, env),
2653
2654 Expr::Do(stmts) => {
2655 let mut local = env.clone();
2656 Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
2657 },
2658
2659 Expr::BinOp(op, lhs, rhs) => {
2660 let l = self.eval_expr(lhs, env)?;
2661 let r = self.eval_expr(rhs, env)?;
2662 self.apply_binop(op, l, r)
2663 },
2664
2665 Expr::If { cond, then, elseifs, else_body } => {
2666 let cond_val = self.eval_expr(cond, env)?;
2667 if self.is_truthy(&cond_val) {
2668 return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
2669 }
2670 for (ei_cond, ei_body) in elseifs {
2671 let ei_cond_val = self.eval_expr(ei_cond, env)?;
2672 if self.is_truthy(&ei_cond_val) {
2673 return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
2674 }
2675 }
2676 if let Some(eb) = else_body {
2677 return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
2678 }
2679 Ok(Value::Unit)
2680 },
2681
2682 Expr::While { cond, body } => {
2683 loop {
2687 let cv = self.eval_expr(cond, env)?;
2688 if !self.is_truthy(&cv) {
2689 break;
2690 }
2691 match self.exec_block(body, env) {
2692 Ok(_) => {},
2693 Err(EvalErr::Break) => break,
2694 Err(e) => return Err(e),
2695 }
2696 }
2697 Ok(Value::Unit)
2698 },
2699
2700 Expr::For { var, iter, body } => {
2701 let iter_val = self.eval_expr(iter, env)?;
2702 let items = self.value_to_iter(iter_val)?;
2703 for item in items {
2704 let mut local = env.clone();
2705 local.insert(var.clone(), item);
2706 match self.exec_block(body, &mut local) {
2707 Ok(_) => {},
2708 Err(EvalErr::Break) => break,
2709 Err(e) => return Err(e),
2710 }
2711 }
2712 Ok(Value::Unit)
2713 },
2714
2715 Expr::Match(subject, arms) => {
2716 let subj = self.eval_expr(subject, env)?;
2717 for arm in arms {
2718 if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
2719 let mut local = env.clone();
2720 local.extend(bindings);
2721 return self.eval_expr(&arm.body, &mut local);
2722 }
2723 }
2724 Ok(Value::Unit)
2725 },
2726
2727 Expr::Range(lo, hi) => {
2728 let lo_v = self.eval_expr(lo, env)?;
2729 let hi_v = self.eval_expr(hi, env)?;
2730 let lo_n = self.to_number(&lo_v)? as i64;
2731 let hi_n = self.to_number(&hi_v)? as i64;
2732 Ok(Value::List(Rc::new(
2733 (lo_n..hi_n).map(|i| Value::Number(i as f64)).collect(),
2734 )))
2735 },
2736
2737 Expr::Index(base, idx) => {
2738 let b = self.eval_expr(base, env)?;
2739 let i = self.eval_expr(idx, env)?;
2740 let n = self.to_number(&i)? as usize;
2741 match b {
2742 Value::List(v) => v
2743 .get(n)
2744 .cloned()
2745 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2746 Value::Str(s) => s
2747 .chars()
2748 .nth(n)
2749 .map(|c| Value::Str(c.to_string()))
2750 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2751 other => Err(EvalErr::from(format!("cannot index {:?}", other))),
2752 }
2753 },
2754
2755 Expr::Call(callee, args) => {
2756 let arg_vals: Vec<Value> = args
2757 .iter()
2758 .map(|a| self.eval_expr(a, env))
2759 .collect::<Result<_, _>>()?;
2760 match callee.as_ref() {
2761 Expr::Ident(name) => self.call_named(name, arg_vals, env),
2762 Expr::Path(segs) => self.call_named(&segs.join("::"), arg_vals, env),
2763 _ => {
2764 let v = self.eval_expr(callee, env)?;
2765 self.call_value(v, arg_vals)
2766 },
2767 }
2768 },
2769
2770 Expr::MethodCall { receiver, method, args } => {
2771 let recv = self.eval_expr(receiver, env)?;
2772 let arg_vals: Vec<Value> = args
2773 .iter()
2774 .map(|a| self.eval_expr(a, env))
2775 .collect::<Result<_, _>>()?;
2776 self.call_method(recv, method, arg_vals)
2777 },
2778
2779 Expr::Closure(params, body) => Ok(Value::Fn(
2780 params.clone(),
2781 vec![Stmt::Expr(*body.clone())],
2782 env.clone(),
2783 )),
2784
2785 Expr::Asm(_) => Ok(Value::Unit),
2786 }
2787 }
2788
2789 fn exec_block(&mut self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
2792 let mut last: Option<Value> = None;
2793 for stmt in stmts {
2794 match stmt {
2795 Stmt::Bind(name, expr) => {
2796 match self.try_inplace_list_update(name, expr, env)? {
2797 Some(v) => env.insert(name.clone(), v),
2798 None => {
2799 let v = self.eval_expr(expr, env)?;
2800 env.insert(name.clone(), v)
2801 },
2802 };
2803 last = None;
2804 },
2805 Stmt::Return(expr) => {
2806 let v = self.eval_expr(expr, env)?;
2807 return Err(EvalErr::Return(v));
2808 },
2809 Stmt::Expr(expr) => {
2810 last = Some(self.eval_expr(expr, env)?);
2811 },
2812 }
2813 }
2814 Ok(last)
2815 }
2816
2817 fn try_inplace_list_update(
2824 &mut self,
2825 name: &str,
2826 expr: &Expr,
2827 env: &mut Env,
2828 ) -> Result<Option<Value>, EvalErr> {
2829 let Expr::Call(callee, args) = expr else { return Ok(None) };
2830 let Expr::Ident(fname) = callee.as_ref() else { return Ok(None) };
2831 let is_push = matches!(
2832 fname.as_str(),
2833 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가"
2834 );
2835 let is_set = matches!(
2836 fname.as_str(),
2837 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정"
2838 );
2839 if !is_push && !is_set {
2840 return Ok(None);
2841 }
2842 match args.first() {
2845 Some(Expr::Ident(a0)) if a0 == name => {},
2846 _ => return Ok(None),
2847 }
2848 if self.functions.contains_key(fname.as_str()) {
2849 return Ok(None);
2850 }
2851 if is_push {
2852 if args.len() != 2 {
2853 return Ok(None);
2854 }
2855 let val = self.eval_expr(&args[1], env)?;
2856 match env.remove(name) {
2857 Some(Value::List(mut v)) => {
2858 Rc::make_mut(&mut v).push(val);
2859 Ok(Some(Value::List(v)))
2860 },
2861 other => {
2862 if let Some(o) = other {
2863 env.insert(name.to_string(), o);
2864 }
2865 Ok(None)
2866 },
2867 }
2868 } else {
2869 if args.len() != 3 {
2870 return Ok(None);
2871 }
2872 let idx_v = self.eval_expr(&args[1], env)?;
2873 let idx = self.to_number(&idx_v).unwrap_or(0.0) as usize;
2874 let val = self.eval_expr(&args[2], env)?;
2875 match env.remove(name) {
2876 Some(Value::List(mut v)) => {
2877 if idx < v.len() {
2878 Rc::make_mut(&mut v)[idx] = val;
2879 }
2880 Ok(Some(Value::List(v)))
2881 },
2882 other => {
2883 if let Some(o) = other {
2884 env.insert(name.to_string(), o);
2885 }
2886 Ok(None)
2887 },
2888 }
2889 }
2890 }
2891
2892 fn lookup(&self, name: &str, env: &Env) -> EvalResult {
2895 if let Some(v) = env.get(name) {
2896 return Ok(v.clone());
2897 }
2898 if let Some(v) = self.global_seed.get(name) {
2901 return Ok(v.clone());
2902 }
2903 if self.functions.contains_key(name) {
2904 let def = &self.functions[name];
2905 return Ok(Value::Fn(def.params.clone(), def.body.clone(), new_env()));
2906 }
2907 if let Some((enum_name, 0)) = self.enum_variants.get(name).cloned() {
2909 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
2910 return Ok(Value::Variant { enum_name, variant, payload: Vec::new() });
2911 }
2912 match name {
2914 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی" | "пи" => {
2915 return Ok(Value::Number(std::f64::consts::PI))
2916 },
2917 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
2918 return Ok(Value::Number(std::f64::consts::TAU))
2919 },
2920 _ => {},
2921 }
2922 Err(EvalErr::from(format!("undefined: '{name}'")))
2923 }
2924
2925 pub(crate) fn call_named(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2934 if !ling_profile_enabled() {
2935 return self.call_named_inner(name, args, env);
2936 }
2937 let t0 = crate::runtime::now_secs();
2938 let r = self.call_named_inner(name, args, env);
2939 ling_profile_record(
2940 name,
2941 ((crate::runtime::now_secs() - t0) * 1_000_000_000.0) as u128,
2942 );
2943 r
2944 }
2945
2946 fn call_named_inner(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2947 if let Some(def) = self.functions.get(name).cloned() {
2950 let mut call_env =
2951 FxHashMap::with_capacity_and_hasher(def.params.len(), Default::default());
2952 let _ = env; for (param, arg) in def.params.iter().zip(args) {
2954 call_env.insert(param.clone(), arg);
2955 }
2956 return match self.framed(name, |me| me.exec_block(&def.body, &mut call_env)) {
2957 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
2958 Err(EvalErr::Return(v)) => Ok(v),
2959 Err(e) => Err(e),
2960 };
2961 }
2962
2963 #[cfg(target_arch = "wasm32")]
2964 if let Some(v) = self.wasm_music_builtin(name, &args)? {
2965 return Ok(v);
2966 }
2967
2968 match name {
2969 "__ling_global" => {
2972 if let Some(Value::Str(g)) = args.first() {
2973 if let Some(v) = self.global_seed.get(g.as_str()) {
2974 return Ok(v.clone());
2975 }
2976 }
2977 return Ok(Value::Unit);
2978 },
2979 "print" | "println" | "印" | "打印" | "印刷" | "พิมพ์" | "출력" | "вывести"
2981 | "imprimir" | "afficher" | "چاپ" | "اطبع" | "הדפס" | "چھاپو" | "drucken" | "печать" => {
2982 let s = args
2983 .iter()
2984 .map(|v| v.to_string())
2985 .collect::<Vec<_>>()
2986 .join("");
2987 println!("{s}");
2988 return Ok(Value::Unit);
2989 },
2990 "print_color" | "พิมพ์สี" | "چاپ_رنگی" | "اطبع_بلون" | "הדפס_בצבע" | "رنگین_چھاپو" => {
2993 #[cfg(windows)]
2994 {
2995 use std::sync::Once;
2996 static VT: Once = Once::new();
2997 VT.call_once(|| {
2998 extern "system" {
2999 fn GetStdHandle(n: u32) -> *mut std::ffi::c_void;
3000 fn GetConsoleMode(h: *mut std::ffi::c_void, m: *mut u32) -> i32;
3001 fn SetConsoleMode(h: *mut std::ffi::c_void, m: u32) -> i32;
3002 }
3003 unsafe {
3004 let h = GetStdHandle(0xFFFF_FFF5u32); let mut mode = 0u32;
3006 if GetConsoleMode(h, &mut mode) != 0 {
3007 SetConsoleMode(h, mode | 0x0004); }
3009 }
3010 });
3011 }
3012 let col = self.arg_num(&args, 0, 7.0)? as i64;
3013 let s = args
3014 .iter()
3015 .skip(1)
3016 .map(|v| v.to_string())
3017 .collect::<Vec<_>>()
3018 .join("");
3019 let code = 90 + col.clamp(0, 7);
3020 println!("\x1b[1;{code}m{s}\x1b[0m");
3021 return Ok(Value::Unit);
3022 },
3023 "format"
3025 | "格式"
3026 | "フォーマット"
3027 | "서식"
3028 | "รูปแบบ"
3029 | "форматировать"
3030 | "formatear"
3031 | "formater" | "قالببندی" | "نسّق" | "פרמט" | "فارمیٹ" | "formatieren" => {
3032 return Ok(Value::Str(self.builtin_format(&args)?));
3033 },
3034 "格式::拼接" | "format::join" => match args.first() {
3036 Some(Value::List(items)) => {
3037 return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
3038 },
3039 _ => return Ok(Value::Str(self.builtin_format(&args)?)),
3040 },
3041 "ok" | "好" | "良し" | "좋아" | "โอเค" | "تایید" | "تمام" | "בסדר" | "ٹھیک" | "bon" | "gut" | "хорошо" => {
3043 let val = args.into_iter().next().unwrap_or(Value::Unit);
3044 return Ok(Value::Ok(Box::new(val)));
3045 },
3046 "bad" | "坏" | "err" | "悪い" | "나쁨" | "ผิด" | "بد" | "سيء" | "רע" | "برا" | "mauvais" | "schlecht" | "плохо" => {
3047 let val = args.into_iter().next().unwrap_or(Value::Unit);
3048 return Ok(Value::Err(Box::new(val)));
3049 },
3050 "向量::从" | "Vec::from" => {
3052 if let Some(Value::List(v)) = args.first() {
3053 return Ok(Value::List(v.clone()));
3054 }
3055 return Ok(Value::List(Rc::new(args)));
3056 },
3057 "向量::有容量" | "Vec::with_capacity" => {
3058 return Ok(Value::List(Rc::new(Vec::new())))
3059 },
3060 "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
3062 "计时::现在" | "Timer::now" => return Ok(Value::Number(1000.0)),
3063 "sleep" | "หยุด" | "นอน" | "sleep_ms" | "睡眠" | "眠る" | "スリープ" | "잠자기"
3065 | "잠" | "流水::睡眠" | "Flow::sleep" | "خواب" | "نم" | "שינה" | "سو_جاؤ" | "dormir" | "schlafen" | "спать" => {
3066 if let Some(ms_val) = args.first() {
3067 if let Ok(ms) = self.to_number(ms_val) {
3068 #[cfg(target_arch = "wasm32")]
3069 wasm_sleep_ms(ms.max(0.0) as i32);
3070 #[cfg(not(target_arch = "wasm32"))]
3071 std::thread::sleep(std::time::Duration::from_millis(ms as u64));
3072 }
3073 }
3074 return Ok(Value::Unit);
3075 },
3076 "流水::并行" | "Flow::parallel" => {
3078 if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
3079 let _ = params;
3080 match self.exec_block(&body, &mut cap) {
3081 Ok(Some(v)) => return Ok(v),
3082 Ok(None) => return Ok(Value::Unit),
3083 Err(EvalErr::Return(v)) => return Ok(v),
3084 Err(e) => return Err(e),
3085 }
3086 }
3087 return Ok(Value::Unit);
3088 },
3089
3090 "sin" | "ไซน์" | "正弦" | "サイン" | "사인" | "سینوس" | "جا" | "סינוס" | "سائن" | "sinus" | "синус" => {
3099 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
3100 },
3101 "cos" | "โคไซน์" | "余弦" | "コサイン" | "코사인" | "کسینوس" | "جتا" | "קוסינוס" | "کوسائن" | "cosinus" | "kosinus" | "косинус" => {
3102 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
3103 },
3104
3105 "tanh" | "tanhf" | "双曲正切" | "双曲線正接" | "쌍곡탄젠트" => {
3108 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
3109 },
3110
3111 "tan" | "แทนเจนต์" | "正切" | "タンジェント" | "탄젠트" | "تانژانت" | "ظا" | "טנגנס" | "ٹینجنٹ" | "tangente" | "tangens" | "тангенс" => {
3112 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
3113 },
3114 "asin" | "arcsin" | "反正弦" | "アークサイン" | "아크사인" | "อาร์กไซน์" | "آرکسینوس" | "قوس_جا" | "ארקסינוס" | "آرک_سائن" | "arcsinus" | "arkussinus" | "арксинус" =>
3115 {
3116 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
3117 },
3118 "acos" | "arccos" | "反余弦" | "アークコサイン" | "아크코사인" | "อาร์กโคไซน์" | "آرککسینوس" | "قوس_جتا" | "ארקוקוסינוס" | "آرک_کوسائن" | "arccosinus" | "arkuskosinus" | "арккосинус" =>
3119 {
3120 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
3121 },
3122 "atan" | "arctan" | "反正切" | "アークタンジェント" | "아크탄젠트" | "อาร์กแทนเจนต์" | "آرکتانژانت" | "قوس_ظا" | "ארקטנגנס" | "آرک_ٹینجنٹ" | "arctangente" | "arkustangens" | "арктангенс" =>
3123 {
3124 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
3125 },
3126 "atan2" | "arctan2" | "反正切2" | "アークタンジェント2" | "아크탄젠트2" =>
3127 {
3128 let y = self.arg_num(&args, 0, 0.0)?;
3129 let x = self.arg_num(&args, 1, 1.0)?;
3130 return Ok(Value::Number(y.atan2(x)));
3131 },
3132
3133 "sqrt" | "รากที่สอง" | "平方根" | "根" | "제곱근" | "جذر" | "שורש" | "racine_carrée" | "quadratwurzel" | "корень" => {
3135 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
3136 },
3137 "cbrt" | "立方根" | "세제곱근" | "รากที่สาม" | "ریشه_سوم" | "جذر_تكعيبي" | "שורש_שלישי" | "مکعب_جذر" | "racine_cubique" | "kubikwurzel" | "кубический_корень" => {
3138 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
3139 },
3140 "pow" | "ยกกำลัง" | "幂" | "べき乗" | "거듭제곱" | "توان" | "أس" | "חזקה" | "قوت" | "puissance" | "potenz" | "степень" => {
3141 let base = self.arg_num(&args, 0, 0.0)?;
3142 let exp = self.arg_num(&args, 1, 1.0)?;
3143 return Ok(Value::Number(base.powf(exp)));
3144 },
3145 "exp" | "指数" | "指数関数" | "지수" => {
3146 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
3147 },
3148 "hypot" | "斜边" | "斜辺" | "빗변" => {
3149 let x = self.arg_num(&args, 0, 0.0)?;
3150 let y = self.arg_num(&args, 1, 0.0)?;
3151 return Ok(Value::Number(x.hypot(y)));
3152 },
3153
3154 "ln" | "log" | "ลอการิทึม" | "对数" | "対数" | "로그" | "لگاریتم_طبیعی" | "لوغاريتم_طبيعي" | "לוגריתם_טבעי" | "فطری_لوگ" => {
3156 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
3157 },
3158 "log2" | "对数2" | "対数2" | "로그2" => {
3159 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
3160 },
3161 "log10" | "对数10" | "対数10" | "로그10" => {
3162 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
3163 },
3164
3165 "abs" | "ค่าสัมบูรณ์" | "绝对值" | "绝对" | "絶対値" | "절댓값" | "절대값" | "قدرمطلق" | "مطلق" | "ערך_מוחלט" | "مطلق_قدر" | "valeur_absolue" | "betrag" | "модуль_числа" =>
3167 {
3168 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
3169 },
3170 "floor" | "ปัดลง" | "向下取整" | "下整" | "床関数" | "내림" | "کف" | "أرضية" | "רצפה" | "فرش" | "plancher" | "abrunden" | "вниз" => {
3171 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
3172 },
3173 "ceil" | "ปัดขึ้น" | "向上取整" | "上整" | "天井関数" | "올림" | "سقف" | "תקרה" | "چھت" | "plafond" | "aufrunden" | "вверх" =>
3174 {
3175 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
3176 },
3177 "round" | "ปัดเศษ" | "四舍五入" | "四舍" | "四捨五入" | "반올림" | "گرد_کردن" | "تقريب" | "עיגול" | "گول" | "arrondir" | "runden" | "округлить" =>
3178 {
3179 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
3180 },
3181 "trunc"
3182 | "int"
3183 | "ตัดทศนิยม"
3184 | "取整"
3185 | "整数化"
3186 | "整数"
3187 | "截整"
3188 | "정수화"
3189 | "정수"
3190 | "切り捨て"
3191 | "버림" | "برش" | "اقتطاع" | "קיטום" | "کٹائی" | "tronquer" | "abschneiden" | "усечь" => {
3192 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
3193 },
3194 "fract" | "小数部分" | "小数部" | "소수부" => {
3195 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
3196 },
3197
3198 "min" | "ต่ำสุด" | "最小" | "최솟값" | "کمینه" | "أصغر" | "מינימום" | "کم_ترین" | "minimum" | "минимум" => {
3200 let a = self.arg_num(&args, 0, 0.0)?;
3201 let b = self.arg_num(&args, 1, 0.0)?;
3202 return Ok(Value::Number(a.min(b)));
3203 },
3204 "max" | "สูงสุด" | "最大" | "최댓값" | "بیشینه" | "أكبر" | "מקסימום" | "زیادہ_ترین" | "maximum" | "максимум" => {
3205 let a = self.arg_num(&args, 0, 0.0)?;
3206 let b = self.arg_num(&args, 1, 0.0)?;
3207 return Ok(Value::Number(a.max(b)));
3208 },
3209 "clamp" | "จำกัด" | "截取" | "範囲制限" | "범위제한" | "محدود" | "قيّد" | "הגבל" | "limiter" | "begrenzen" | "ограничить" => {
3210 let x = self.arg_num(&args, 0, 0.0)?;
3211 let lo = self.arg_num(&args, 1, 0.0)?;
3212 let hi = self.arg_num(&args, 2, 1.0)?;
3213 return Ok(Value::Number(x.clamp(lo, hi)));
3214 },
3215
3216 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی" | "пи" => {
3218 return Ok(Value::Number(std::f64::consts::PI))
3219 },
3220 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
3221 return Ok(Value::Number(std::f64::consts::TAU))
3222 },
3223
3224 "vnoise" | "noise2" | "นอยส์2ดี" | "柏林噪声2D" | "バリューノイズ2D" | "값노이즈2D" | "نویز_برداری" | "ضجيج_متجه" | "רעש_וקטורי" | "ویکٹر_نوائز" | "bruit_v" | "v_rauschen" | "шум_v" =>
3230 {
3231 let x = self.arg_num(&args, 0, 0.0)? as f32;
3232 let y = self.arg_num(&args, 1, 0.0)? as f32;
3233 let seed = self.arg_num(&args, 2, 0.0)? as u32;
3234 return Ok(Value::Number(tex_vnoise(x, y, seed) as f64));
3235 },
3236
3237 "fbm" | "นอยส์ออร์แกนิก" | "分形噪声" | "フラクタルノイズ" | "프랙탈노이즈" | "نویز_فراکتالی" | "ضجيج_عضوي" | "רעש_פרקטלי" | "فریکٹل_نوائز" =>
3238 {
3239 let x = self.arg_num(&args, 0, 0.0)? as f32;
3240 let y = self.arg_num(&args, 1, 0.0)? as f32;
3241 let octaves = self.arg_num(&args, 2, 4.0)? as u32;
3242 let seed = self.arg_num(&args, 3, 0.0)? as u32;
3243 return Ok(Value::Number(tex_fbm(x, y, octaves, seed) as f64));
3244 },
3245
3246 "perlin"
3247 | "perlin3"
3248 | "เพอร์ลิน3ดี"
3249 | "柏林噪声3D"
3250 | "パーリンノイズ3D"
3251 | "펄린노이즈3D" | "نویز_پرلین" | "بيرلين" | "רעש_פרלין" | "پرلن_نوائز" | "перлин" => {
3252 let x = self.arg_num(&args, 0, 0.0)? as f32;
3253 let y = self.arg_num(&args, 1, 0.0)? as f32;
3254 let z = self.arg_num(&args, 2, 0.0)? as f32;
3255 return Ok(Value::Number(perlin3(x, y, z) as f64));
3256 },
3257
3258 "lerp" | "ค่าระหว่าง" | "线性插值" | "線形補間" | "선형보간" | "میانیابی" | "استيفاء" | "אינטרפולציה" | "درمیانی_قدر" | "interpoler" | "interpolieren" | "интерполировать" =>
3260 {
3261 let a = self.arg_num(&args, 0, 0.0)?;
3262 let b = self.arg_num(&args, 1, 1.0)?;
3263 let t = self.arg_num(&args, 2, 0.0)?;
3264 return Ok(Value::Number(a + (b - a) * t));
3265 },
3266
3267 "smoothstep" | "เปลี่ยนแบบนุ่ม" | "平滑步进" | "スムーズステップ" | "스무스스텝" | "گام_نرم" | "تدرج_ناعم" | "מדרגה_חלקה" | "ہموار_قدم" | "lissage" | "glättung" | "сглаживание" =>
3268 {
3269 let lo = self.arg_num(&args, 0, 0.0)?;
3270 let hi = self.arg_num(&args, 1, 1.0)?;
3271 let x = self.arg_num(&args, 2, 0.5)?;
3272 let t = ((x - lo) / (hi - lo)).clamp(0.0, 1.0);
3273 return Ok(Value::Number(t * t * (3.0 - 2.0 * t)));
3274 },
3275
3276 "rand" | "สุ่ม" | "随机" | "乱数" | "난수" | "تصادفی" | "عشوائي" | "אקראי" | "بے_ترتیب" | "aléatoire" | "zufall" | "случайное" => {
3277 let val = fast_rand_f64(&mut self.rand_state);
3278 return Ok(Value::Number(val));
3279 },
3280
3281 "sign" | "เครื่องหมาย" | "符号" | "符号関数" | "부호" | "علامت" | "إشارة" | "סימן" | "نشان" | "signe" | "vorzeichen" | "знак" => {
3282 let x = self.arg_num(&args, 0, 0.0)?;
3283 return Ok(Value::Number(x.signum()));
3284 },
3285
3286 "hsv_to_rgb" | "เอชเอสวีเป็นRGB" | "HSV转RGB" | "HSV変換RGB" | "HSV변환RGB" | "HSV_به_RGB" | "HSV_إلى_RGB" | "HSV_ל_RGB" | "HSV_سے_RGB" | "hsv_vers_rgb" | "hsv_zu_rgb" | "hsv_в_rgb" =>
3287 {
3288 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;
3292 let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
3293 let m = v - c;
3294 let (r1, g1, b1) = if h < 60.0 {
3295 (c, x, 0.0)
3296 } else if h < 120.0 {
3297 (x, c, 0.0)
3298 } else if h < 180.0 {
3299 (0.0, c, x)
3300 } else if h < 240.0 {
3301 (0.0, x, c)
3302 } else if h < 300.0 {
3303 (x, 0.0, c)
3304 } else {
3305 (c, 0.0, x)
3306 };
3307 let r = ((r1 + m) * 255.0).round();
3308 let g = ((g1 + m) * 255.0).round();
3309 let b = ((b1 + m) * 255.0).round();
3310 return Ok(Value::List(Rc::new(vec![
3311 Value::Number(r),
3312 Value::Number(g),
3313 Value::Number(b),
3314 ])));
3315 },
3316
3317 "lerp_color" | "ไล่สี" | "颜色插值" | "色補間" | "색보간" | "میانیابی_رنگ" | "استيفاء_اللون" | "אינטרפולציית_צבע" | "رنگ_درمیانی_قدر" | "interpoler_couleur" | "farbe_interpolieren" | "интерполировать_цвет" => {
3318 let r1 = self.arg_num(&args, 0, 0.0)?;
3319 let g1 = self.arg_num(&args, 1, 0.0)?;
3320 let b1 = self.arg_num(&args, 2, 0.0)?;
3321 let r2 = self.arg_num(&args, 3, 255.0)?;
3322 let g2 = self.arg_num(&args, 4, 255.0)?;
3323 let b2 = self.arg_num(&args, 5, 255.0)?;
3324 let t = self.arg_num(&args, 6, 0.0)?;
3325 let r = r1 + (r2 - r1) * t;
3326 let g = g1 + (g2 - g1) * t;
3327 let b = b1 + (b2 - b1) * t;
3328 let c = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
3329 self.gfx.borrow_mut().color = c;
3330 return Ok(Value::Unit);
3331 },
3332
3333 "time_now" | "เวลาปัจจุบัน" | "当前时间" | "経過時間" | "현재시간" | "زمان_اکنون" | "الوقت_الآن" | "הזמן_עכשיו" | "ابھی_کا_وقت" | "temps_actuel" | "aktuelle_zeit" | "текущее_время" =>
3335 {
3336 return Ok(Value::Number(
3337 crate::runtime::now_secs() - self.start_time_secs,
3338 ));
3339 },
3340
3341 "epoch_now" | "เวลาโลก" | "datetime" | "现在时刻" | "現在時刻" | "현재시각" | "مهر_زمانی_اکنون" | "طابع_الوقت_الآن" | "חותמת_זמן_עכשיו" | "ایپاک_وقت" =>
3345 {
3346 return Ok(Value::Number(crate::runtime::now_secs()));
3347 },
3348
3349 "frame_count" | "เฟรม" | "帧数" | "フレーム数" | "프레임수" | "شمار_فریم" | "عدد_الإطارات" | "ספירת_פריימים" | "فریم_شمار" | "nombre_images" | "bildanzahl" | "число_кадров" => {
3350 return Ok(Value::Number(self.frame_num as f64));
3351 },
3352
3353 "mic_open" | "เปิดไมค์" | "开麦克风" | "マイク開く" | "마이크열기" | "باز_کردن_میکروفون" | "افتح_الميكروفون" | "פתח_מיקרופון" | "مائیکروفون_کھولو" | "ouvrir_micro" | "mikrofon_öffnen" | "открыть_микрофон" =>
3355 {
3356 #[cfg(not(target_arch = "wasm32"))]
3357 {
3358 match ling_mic::MicInput::open(Default::default()) {
3359 Ok(mic) => {
3360 let _ = mic.start(|_samples: &[f32]| {}); self.mic = Some(mic);
3362 return Ok(Value::Number(1.0)); },
3364 Err(_e) => {
3367 self.mic = None;
3368 return Ok(Value::Number(0.0));
3369 },
3370 }
3371 }
3372 #[cfg(target_arch = "wasm32")]
3373 return Ok(Value::Unit);
3374 },
3375
3376 "mic_rms" | "เสียงRMS" | "麦克风音量" | "マイクRMS" | "마이크RMS" | "RMS_میکروفون" | "RMS_الميكروفون" | "RMS_מיקרופון" | "مائیکروفون_RMS" | "rms_micro" | "mikrofon_rms" | "rms_микрофона" =>
3377 {
3378 #[cfg(not(target_arch = "wasm32"))]
3379 {
3380 let rms = self
3381 .mic
3382 .as_ref()
3383 .map(|m: &ling_mic::MicInput| m.rms())
3384 .unwrap_or(0.0);
3385 return Ok(Value::Number(rms as f64));
3386 }
3387 #[cfg(target_arch = "wasm32")]
3388 return Ok(Value::Number(0.0));
3389 },
3390
3391 "mic_peak" | "เสียงพีค" | "麦克风峰值" | "マイクピーク" | "마이크피크" | "اوج_میکروفون" | "ذروة_الميكروفون" | "שיא_מיקרופון" | "مائیکروفون_چوٹی" | "crête_micro" | "mikrofon_spitze" | "пик_микрофона" =>
3392 {
3393 #[cfg(not(target_arch = "wasm32"))]
3394 {
3395 let peak = self
3396 .mic
3397 .as_ref()
3398 .map(|m: &ling_mic::MicInput| m.peak())
3399 .unwrap_or(0.0);
3400 return Ok(Value::Number(peak as f64));
3401 }
3402 #[cfg(target_arch = "wasm32")]
3403 return Ok(Value::Number(0.0));
3404 },
3405
3406 "mic_fft" | "วิเคราะห์เสียงสด" | "实时频谱" | "リアルタイムFFT" | "실시간FFT" | "FFT_میکروفون" | "FFT_الميكروفون" | "FFT_מיקרופון" | "مائیکروفون_FFT" | "fft_micro" | "mikrofon_fft" | "fft_микрофона" =>
3407 {
3408 #[cfg(not(target_arch = "wasm32"))]
3409 {
3410 let n = self.arg_num(&args, 0, 8.0)? as usize;
3411 if let Some(mic) = self.mic.as_ref() {
3412 let samples = mic.latest_samples();
3413 self.fft.borrow_mut().push_samples(&samples);
3414 }
3415 let bands = self.fft.borrow().freq_bands(n);
3416 let result: Vec<Value> =
3417 bands.iter().map(|&v| Value::Number(v as f64)).collect();
3418 return Ok(Value::List(Rc::new(result)));
3419 }
3420 #[cfg(target_arch = "wasm32")]
3421 return Ok(Value::List(Vec::new().into()));
3422 },
3423
3424 "set_blend" | "โหมดผสม" | "混合模式" | "ブレンドモード" | "블렌드모드" | "تنظیم_ترکیب" | "عيّن_المزج" | "קבע_מיזוג" | "بلینڈ_مقرر_کرو" | "définir_mélange" | "mischmodus_setzen" | "задать_смешивание" =>
3426 {
3427 let mode = self.arg_num(&args, 0, 0.0)? as u8;
3428 let mut gfx = self.gfx.borrow_mut();
3429 gfx.blend = mode;
3430 let a = gfx.alpha;
3431 gfx.depth_queue.set_state(mode, a); return Ok(Value::Unit);
3433 },
3434
3435 "set_antialias" | "ตั้งลบรอยหยัก" | "抗锯齿" | "アンチエイリアス" | "안티에일리어싱" | "تنظیم_ضدلبهدندانه" | "عيّن_مضاد_التسنن" | "קבע_החלקת_קצוות" | "اینٹی_الائیسنگ_مقرر_کرو" =>
3439 {
3440 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3441 self.gfx.borrow_mut().antialias = on;
3442 return Ok(Value::Unit);
3443 },
3444 "get_antialias"
3446 | "อ่านลบรอยหยัก"
3447 | "读取抗锯齿"
3448 | "アンチエイリアス取得"
3449 | "안티에일리어싱상태" | "خواندن_ضدلبهدندانه" | "اقرأ_مضاد_التسنن" | "קרא_החלקת_קצוות" | "اینٹی_الائیسنگ_پڑھو" => {
3450 return Ok(Value::Bool(self.gfx.borrow().antialias));
3451 },
3452
3453 "set_font_antialias" | "글꼴안티에일리어싱" => {
3457 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3458 self.gfx.borrow_mut().font_antialias = on;
3459 return Ok(Value::Unit);
3460 },
3461 "get_font_antialias" | "글꼴안티에일리어싱상태" => {
3463 return Ok(Value::Bool(self.gfx.borrow().font_antialias));
3464 },
3465
3466 "draw_circle" | "วาดวงกลม" | "画圆" | "円描画" | "원그리기" | "رسم_دایره" | "ارسم_دائرة" | "צייר_עיגול" | "دائرہ_کھینچو" | "dessiner_cercle" | "kreis_zeichnen" | "рисовать_круг" =>
3468 {
3469 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3470 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3471 let r = self.arg_num(&args, 2, 10.0)? as i32;
3472 let mut gfx = self.gfx.borrow_mut();
3473 let (w, h, color, blend) =
3474 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3475 if gfx.antialias {
3476 let (uw, uh) = (gfx.width, gfx.height);
3477 let segs = ((r.max(1) as u32) * 4).clamp(24, 512);
3478 crate::gfx::raster::draw_arc(
3479 &mut gfx.buffer,
3480 uw,
3481 uh,
3482 color,
3483 true,
3484 blend == 1,
3485 cx as f32,
3486 cy as f32,
3487 r as f32,
3488 0.0,
3489 std::f32::consts::TAU,
3490 segs,
3491 );
3492 } else {
3493 draw_circle_outline(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3494 }
3495 return Ok(Value::Unit);
3496 },
3497
3498 "draw_filled_circle"
3499 | "draw_disc"
3500 | "วาดวงกลมทึบ"
3501 | "画实心圆"
3502 | "塗りつぶし円"
3503 | "원채우기" | "رسم_دایره_توپر" | "ارسم_دائرة_ممتلئة" | "צייר_עיגול_מלא" | "بھرا_دائرہ_کھینچو" => {
3504 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3505 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3506 let r = self.arg_num(&args, 2, 10.0)? as i32;
3507 let mut gfx = self.gfx.borrow_mut();
3508 let (w, h, color, blend) =
3509 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3510 draw_circle_filled(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3511 return Ok(Value::Unit);
3512 },
3513
3514 "draw_arc" | "arc" | "วาดส่วนโค้ง" | "画弧" | "円弧描画" | "호그리기" | "رسم_کمان" | "ارسم_قوسا" | "צייר_קשת" | "آرک_کھینچو" =>
3518 {
3519 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3520 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3521 let r = self.arg_num(&args, 2, 10.0)? as f32;
3522 let a0 = self.arg_num(&args, 3, 0.0)? as f32;
3523 let a1 = self.arg_num(&args, 4, std::f64::consts::TAU)? as f32;
3524 let default_segs = ((r.abs() * (a1 - a0).abs()).ceil() as u32).clamp(8, 1024);
3525 let segs = self.arg_num(&args, 5, default_segs as f64)? as u32;
3526 let mut gfx = self.gfx.borrow_mut();
3527 let color = gfx.color;
3528 #[cfg(not(target_arch = "wasm32"))]
3529 {
3530 let (uw, uh, aa, add) = (gfx.width, gfx.height, gfx.antialias, gfx.blend == 1);
3531 crate::gfx::raster::draw_arc(
3532 &mut gfx.buffer,
3533 uw,
3534 uh,
3535 color,
3536 aa,
3537 add,
3538 cx,
3539 cy,
3540 r,
3541 a0,
3542 a1,
3543 segs,
3544 );
3545 }
3546 #[cfg(target_arch = "wasm32")]
3547 {
3548 let segs_f = segs.max(1);
3549 let step = (a1 - a0) / segs_f as f32;
3550 let mut px = cx + r * a0.cos();
3551 let mut py = cy + r * a0.sin();
3552 let mut i = 1u32;
3553 while i <= segs_f {
3554 let a = a0 + step * i as f32;
3555 let nx = cx + r * a.cos();
3556 let ny = cy + r * a.sin();
3557 gfx.depth_queue.push_line(0.0, color, px, py, nx, ny);
3558 px = nx;
3559 py = ny;
3560 i += 1;
3561 }
3562 }
3563 return Ok(Value::Unit);
3564 },
3565
3566 "set_alpha" | "ตั้งความโปร่งใส" | "设透明" | "アルファ設定" | "투명도설정" | "تنظیم_شفافیت" | "عيّن_الشفافية" | "קבע_שקיפות" | "شفافیت_مقرر_کرو" | "définir_alpha" | "alpha_setzen" | "задать_альфа" =>
3572 {
3573 let a = self.arg_num(&args, 0, 1.0)? as f32;
3574 let mut gfx = self.gfx.borrow_mut();
3575 gfx.alpha = a.clamp(0.0, 1.0);
3576 let (m, al) = (gfx.blend, gfx.alpha);
3577 gfx.depth_queue.set_state(m, al); return Ok(Value::Unit);
3579 },
3580
3581 "mesh_hue" | "หมุนสีเมช" | "فام_مش" | "صبغة_الشبكة" | "גוון_רשת" | "میش_ہیو" =>
3584 {
3585 let h = self.arg_num(&args, 0, 0.0)? as f32;
3586 let g = self.arg_num(&args, 1, 1.0)? as f32;
3587 let mut gfx = self.gfx.borrow_mut();
3588 gfx.mesh_hue = h;
3589 gfx.mesh_hue_gain = g.max(0.0);
3590 return Ok(Value::Unit);
3591 },
3592
3593 "set_frame_blur" | "frame_blur" | "เบลอเฟรม" | "تنظیم_تاری_فریم" | "عيّن_ضبابية_الإطار" | "קבע_טשטוש_פריים" | "فریم_بلر_مقرر_کرو" =>
3596 {
3597 let a = self.arg_num(&args, 0, 0.0)? as f32;
3598 let mut gfx = self.gfx.borrow_mut();
3599 gfx.frame_blur = a.clamp(0.0, 0.95);
3600 if gfx.frame_blur <= 0.0 {
3601 gfx.prev_frame = Vec::new();
3602 }
3603 return Ok(Value::Unit);
3604 },
3605
3606 "set_line_hue_cycle" | "ตั้งวนสีเส้น" | "تنظیم_چرخه_فام_خط" | "عيّن_دورة_صبغة_الخط" | "קבע_מחזור_גוון_קו" | "لائن_ہیو_سائیکل_مقرر_کرو" => {
3610 let rate = self.arg_num(&args, 0, 0.0)?;
3611 crate::runtime::set_line_hue_rate(rate);
3612 return Ok(Value::Unit);
3613 },
3614
3615 "set_color_space" | "ปริภูมิสี" | "色彩空间" | "色空間" | "색공간" | "تنظیم_فضای_رنگ" | "عيّن_فضاء_اللون" | "קבע_מרחב_צבע" | "کلر_اسپیس_مقرر_کرو" | "définir_espace_couleur" | "farbraum_setzen" | "задать_цветовое_пространство" =>
3619 {
3620 let m = self.arg_num(&args, 0, 0.0)? as i64;
3621 self.gfx.borrow_mut().linear_blend = m != 0;
3622 return Ok(Value::Unit);
3623 },
3624
3625 "set_gradient_space" | "ปริภูมิไล่สี" | "渐变空间" | "グラデ空間" | "그라데이션공간" | "تنظیم_فضای_گرادیان" | "عيّن_فضاء_التدرج" | "קבע_מרחב_גרדיאנט" | "گریڈینٹ_اسپیس_مقرر_کرو" | "définir_espace_dégradé" | "verlaufsraum_setzen" | "задать_пространство_градиента" =>
3628 {
3629 let m = self.arg_num(&args, 0, 1.0)? as i64;
3630 self.gfx.borrow_mut().grad_oklab = m != 0;
3631 return Ok(Value::Unit);
3632 },
3633
3634 "mix_color" | "ผสมสี" | "混合颜色" | "色混合" | "색혼합" | "ترکیب_رنگ" | "امزج_اللون" | "ערבב_צבע" | "رنگ_ملاؤ" | "mélanger_couleur" | "farbe_mischen" | "смешать_цвет" => {
3638 let c0 = rgb(
3639 self.arg_num(&args, 0, 0.0)?,
3640 self.arg_num(&args, 1, 0.0)?,
3641 self.arg_num(&args, 2, 0.0)?,
3642 );
3643 let c1 = rgb(
3644 self.arg_num(&args, 3, 255.0)?,
3645 self.arg_num(&args, 4, 255.0)?,
3646 self.arg_num(&args, 5, 255.0)?,
3647 );
3648 let t = self.arg_num(&args, 6, 0.5)? as f32;
3649 self.gfx.borrow_mut().color = crate::gfx::color::mix_oklab(c0, c1, t);
3650 return Ok(Value::Unit);
3651 },
3652
3653 "set_depth_test" | "ทดสอบความลึก" | "深度测试" | "深度テスト" | "깊이테스트" | "تنظیم_آزمون_عمق" | "عيّن_اختبار_العمق" | "קבע_בדיקת_עומק" | "ڈیپتھ_ٹیسٹ_مقرر_کرو" | "définir_test_profondeur" | "tiefentest_setzen" | "задать_тест_глубины" =>
3657 {
3658 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3659 self.gfx.borrow_mut().depth_test = on;
3660 return Ok(Value::Unit);
3661 },
3662
3663 "set_flat_shade" | "ตั้งแฟลตเชด" | "平面着色" | "フラット着色" | "평면음영" | "تنظیم_سایهپردازی_تخت" | "عيّن_تظليلا_مسطحا" | "קבע_הצללה_שטוחה" | "فلیٹ_شیڈ_مقرر_کرو" =>
3666 {
3667 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3668 self.gfx.borrow_mut().flat_shade = on;
3669 return Ok(Value::Unit);
3670 },
3671
3672 "set_normal_override" =>
3675 {
3676 let x = self.arg_num(&args, 0, 0.0)? as f32;
3677 let y = self.arg_num(&args, 1, -1.0)? as f32;
3678 let z = self.arg_num(&args, 2, 0.0)? as f32;
3679 self.gfx.borrow_mut().normal_override = Some([x, y, z]);
3680 return Ok(Value::Unit);
3681 },
3682
3683 "reset_normal_override" =>
3684 {
3685 self.gfx.borrow_mut().normal_override = None;
3686 return Ok(Value::Unit);
3687 },
3688
3689 "clear_depth" | "ล้างความลึก" | "清深度" | "深度クリア" | "깊이지우기" | "پاککردن_عمق" | "امسح_العمق" | "נקה_עומק" | "گہرائی_صاف_کرو" =>
3693 {
3694 self.gfx.borrow_mut().zbuf_needs_clear = true;
3695 return Ok(Value::Unit);
3696 },
3697
3698 "depth_blur" | "เบลอความลึก" | "dof" | "depth_of_field" | "景深" | "تاری_عمق" | "ضبابية_العمق" | "טשטוש_עומק" | "ڈیپتھ_بلر" =>
3704 {
3705 let focus = self.arg_num(&args, 0, 30.0)? as f32;
3706 let range = self.arg_num(&args, 1, 60.0)? as f32;
3707 let radius = self.arg_num(&args, 2, 3.0)?.max(0.0) as usize;
3708 let oil = self.arg_num(&args, 3, 0.0)? as f32;
3711 let mut gfx = self.gfx.borrow_mut();
3712 let w = gfx.width;
3713 let h = gfx.height;
3714 if gfx.depth_buf.len() == w * h {
3715 let g = &mut *gfx;
3716 crate::gfx::raster::depth_of_field(
3717 &mut g.buffer,
3718 &g.depth_buf,
3719 w,
3720 h,
3721 focus,
3722 range,
3723 radius,
3724 oil,
3725 );
3726 }
3727 return Ok(Value::Unit);
3728 },
3729
3730 "light_pool" | "แอ่งแสง" | "光池" | "ライトプール" | "빛웅덩이" | "برکه_نور" | "بركة_ضوء" | "בריכת_אור" | "روشنی_تالاب" | "bassin_lumière" | "lichtpfütze" | "лужа_света" =>
3736 {
3737 let x = self.arg_num(&args, 0, 0.0)? as f32;
3738 let y = self.arg_num(&args, 1, 0.0)? as f32;
3739 let z = self.arg_num(&args, 2, 0.0)? as f32;
3740 let radius = self.arg_num(&args, 3, 20.0)? as f32;
3741 let r = self.arg_num(&args, 4, 255.0)? as f32 / 255.0;
3742 let g = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3743 let b = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3744 let inten = self.arg_num(&args, 7, 1.0)? as f32;
3745 self.gfx
3746 .borrow_mut()
3747 .emit_light_pool(x, y, z, radius, [r, g, b], inten);
3748 return Ok(Value::Unit);
3749 },
3750
3751 "light_beam" | "ลำแสงไฟ" | "光柱" | "ライトビーム" | "빛기둥" | "پرتو_نور" | "شعاع_ضوء" | "קרן_אור" | "روشنی_شعاع" | "faisceau_lumière" | "lichtstrahl" | "луч_света" =>
3756 {
3757 let x = self.arg_num(&args, 0, 0.0)? as f32;
3758 let y = self.arg_num(&args, 1, 0.0)? as f32;
3759 let z = self.arg_num(&args, 2, 0.0)? as f32;
3760 let fy = self.arg_num(&args, 3, 0.0)? as f32;
3761 let radius = self.arg_num(&args, 4, 14.0)? as f32;
3762 let r = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3763 let g = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3764 let b = self.arg_num(&args, 7, 255.0)? as f32 / 255.0;
3765 let inten = self.arg_num(&args, 8, 1.0)? as f32;
3766 self.gfx
3767 .borrow_mut()
3768 .emit_light_beam(x, y, z, fy, radius, [r, g, b], inten);
3769 return Ok(Value::Unit);
3770 },
3771
3772 "grad_triangle" | "สามเหลี่ยมไล่สี" | "渐变三角" | "グラデ三角" | "그라데삼각" | "مثلث_گرادیان" | "مثلث_متدرج" | "משולש_גרדיאנט" | "گریڈینٹ_مثلث" | "triangle_dégradé" | "dreieck_verlauf" | "градиент_треугольник" =>
3776 {
3777 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3778 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3779 let c0 = rgb(
3780 self.arg_num(&args, 2, 255.0)?,
3781 self.arg_num(&args, 3, 255.0)?,
3782 self.arg_num(&args, 4, 255.0)?,
3783 );
3784 let x1 = self.arg_num(&args, 5, 0.0)? as f32;
3785 let y1 = self.arg_num(&args, 6, 0.0)? as f32;
3786 let c1 = rgb(
3787 self.arg_num(&args, 7, 255.0)?,
3788 self.arg_num(&args, 8, 255.0)?,
3789 self.arg_num(&args, 9, 255.0)?,
3790 );
3791 let x2 = self.arg_num(&args, 10, 0.0)? as f32;
3792 let y2 = self.arg_num(&args, 11, 0.0)? as f32;
3793 let c2 = rgb(
3794 self.arg_num(&args, 12, 255.0)?,
3795 self.arg_num(&args, 13, 255.0)?,
3796 self.arg_num(&args, 14, 255.0)?,
3797 );
3798 let mut gfx = self.gfx.borrow_mut();
3799 let (w, h, alpha, mode, lin, ok) = (
3800 gfx.width,
3801 gfx.height,
3802 gfx.alpha,
3803 gfx.blend,
3804 gfx.linear_blend,
3805 gfx.grad_oklab,
3806 );
3807 crate::gfx::raster::fill_triangle_grad(
3808 &mut gfx.buffer,
3809 w,
3810 h,
3811 alpha,
3812 mode,
3813 lin,
3814 ok,
3815 x0,
3816 y0,
3817 c0,
3818 x1,
3819 y1,
3820 c1,
3821 x2,
3822 y2,
3823 c2,
3824 );
3825 return Ok(Value::Unit);
3826 },
3827
3828 "grad_rect" | "สี่เหลี่ยมไล่สี" | "渐变矩形" | "グラデ矩形" | "그라데사각" | "مستطیل_گرادیان" | "مستطيل_متدرج" | "מלבן_גרדיאנט" | "گریڈینٹ_مستطیل" | "rectangle_dégradé" | "rechteck_verlauf" | "градиент_прямоугольник" =>
3831 {
3832 let x = self.arg_num(&args, 0, 0.0)? as f32;
3833 let y = self.arg_num(&args, 1, 0.0)? as f32;
3834 let rw = self.arg_num(&args, 2, 0.0)? as f32;
3835 let rh = self.arg_num(&args, 3, 0.0)? as f32;
3836 let c0 = rgb(
3837 self.arg_num(&args, 4, 255.0)?,
3838 self.arg_num(&args, 5, 255.0)?,
3839 self.arg_num(&args, 6, 255.0)?,
3840 );
3841 let c1 = rgb(
3842 self.arg_num(&args, 7, 0.0)?,
3843 self.arg_num(&args, 8, 0.0)?,
3844 self.arg_num(&args, 9, 0.0)?,
3845 );
3846 let dir = self.arg_num(&args, 10, 1.0)? as u8;
3847 let mut gfx = self.gfx.borrow_mut();
3848 let (w, h, alpha, mode, lin, ok) = (
3849 gfx.width,
3850 gfx.height,
3851 gfx.alpha,
3852 gfx.blend,
3853 gfx.linear_blend,
3854 gfx.grad_oklab,
3855 );
3856 crate::gfx::raster::fill_rect_grad(
3857 &mut gfx.buffer,
3858 w,
3859 h,
3860 alpha,
3861 mode,
3862 lin,
3863 ok,
3864 x,
3865 y,
3866 rw,
3867 rh,
3868 c0,
3869 c1,
3870 dir,
3871 );
3872 return Ok(Value::Unit);
3873 },
3874
3875 "shadow_blob" | "เงาวงรี" | "阴影斑" | "影ブロブ" | "그림자블롭" | "لکه_سایه" | "بقعة_ظل" | "כתם_צל" | "سایہ_دھبہ" | "tache_ombre" | "schattenklecks" | "пятно_тени" =>
3879 {
3880 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3881 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3882 let rx = self.arg_num(&args, 2, 16.0)? as f32;
3883 let ry = self.arg_num(&args, 3, 8.0)? as f32;
3884 let a = self.arg_num(&args, 4, 0.5)? as f32;
3885 let mut gfx = self.gfx.borrow_mut();
3886 let (w, h, color, soft, mode, lin) = (
3887 gfx.width,
3888 gfx.height,
3889 gfx.color,
3890 gfx.shadow.soft,
3891 gfx.blend,
3892 gfx.linear_blend,
3893 );
3894 crate::gfx::raster::fill_disc_soft(
3895 &mut gfx.buffer,
3896 w,
3897 h,
3898 cx,
3899 cy,
3900 rx,
3901 ry,
3902 color,
3903 a,
3904 soft,
3905 mode,
3906 lin,
3907 );
3908 return Ok(Value::Unit);
3909 },
3910
3911 "cast_shadow" | "ทอดเงา" | "投射阴影" | "影を落とす" | "그림자드리우기" | "افکندن_سایه" | "ألقِ_ظلا" | "הטל_צל" | "سایہ_ڈالو" | "projeter_ombre" | "schatten_werfen" | "отбросить_тень" =>
3916 {
3917 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3918 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3919 let height = (self.arg_num(&args, 2, 0.0)? as f32).max(0.0);
3920 let mut gfx = self.gfx.borrow_mut();
3921 let sp = gfx.shadow;
3922 let radius = (sp.base + sp.grow * height).max(0.5);
3923 let alpha = (sp.alpha - sp.fade * height).clamp(0.04, 1.0);
3924 let soft = (sp.soft + height * 0.004).clamp(0.0, 0.95);
3925 let (w, h, color, mode, lin) = (
3926 gfx.width,
3927 gfx.height,
3928 gfx.color,
3929 gfx.blend,
3930 gfx.linear_blend,
3931 );
3932 crate::gfx::raster::fill_disc_soft(
3933 &mut gfx.buffer,
3934 w,
3935 h,
3936 cx,
3937 cy,
3938 radius,
3939 radius * 0.62,
3940 color,
3941 alpha,
3942 soft,
3943 mode,
3944 lin,
3945 );
3946 return Ok(Value::Unit);
3947 },
3948
3949 "shadow_params" | "ตั้งค่าเงา" | "阴影参数" | "影設定" | "그림자설정" | "پارامترهای_سایه" | "معاملات_الظل" | "פרמטרי_צל" | "سایہ_پیرامیٹرز" | "paramètres_ombre" | "schattenparameter" | "параметры_тени" =>
3952 {
3953 let cur = self.gfx.borrow().shadow;
3954 let base = self.arg_num(&args, 0, cur.base as f64)? as f32;
3955 let grow = self.arg_num(&args, 1, cur.grow as f64)? as f32;
3956 let alpha = self.arg_num(&args, 2, cur.alpha as f64)? as f32;
3957 let fade = self.arg_num(&args, 3, cur.fade as f64)? as f32;
3958 let soft = self.arg_num(&args, 4, cur.soft as f64)? as f32;
3959 self.gfx.borrow_mut().shadow =
3960 crate::gfx::ShadowParams { base, grow, alpha, fade, soft };
3961 return Ok(Value::Unit);
3962 },
3963
3964 "depth_triangle" | "สามเหลี่ยมเรียงลึก" | "深度三角" | "深度三角形" | "깊이삼각" | "مثلث_عمق" | "مثلث_العمق" | "משולש_עומק" | "گہرائی_مثلث" | "triangle_profondeur" | "tiefendreieck" | "глубина_треугольник" =>
3969 {
3970 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3971 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3972 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3973 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3974 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
3975 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
3976 let z = self.arg_num(&args, 6, 0.0)? as f32;
3977 let mut gfx = self.gfx.borrow_mut();
3978 let color = gfx.color;
3979 gfx.depth_queue
3980 .push_triangle(z, color, x0, y0, x1, y1, x2, y2);
3981 return Ok(Value::Unit);
3982 },
3983
3984 "depth_line" | "เส้นเรียงลึก" | "深度线" | "深度線" | "깊이선" | "خط_عمق" | "خط_العمق" | "קו_עומק" | "گہرائی_لکیر" | "ligne_profondeur" | "tiefenlinie" | "глубина_линия" =>
3987 {
3988 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3989 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3990 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3991 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3992 let z = self.arg_num(&args, 4, 0.0)? as f32;
3993 let mut gfx = self.gfx.borrow_mut();
3994 let color = gfx.color;
3995 gfx.depth_queue.push_line(z, color, x0, y0, x1, y1);
3996 return Ok(Value::Unit);
3997 },
3998
3999 "เปิดหน้าต่าง" | "open_window" | "gfx_window" | "开窗" | "ウィンドウ開く" | "창열기" | "باز_کردن_پنجره" | "افتح_نافذة" | "פתח_חלון" | "ونڈو_کھولو" =>
4006 {
4007 let w = self.arg_num(&args, 0, 800.0)? as usize;
4008 let h = self.arg_num(&args, 1, 600.0)? as usize;
4009 #[cfg(not(target_arch = "wasm32"))]
4010 {
4011 let title = args
4012 .get(2)
4013 .map(|v| v.to_string())
4014 .unwrap_or_else(|| "Ling".into());
4015 let mut gfx = self.gfx.borrow_mut();
4016 let mut win = minifb::Window::new(
4017 &title,
4018 w,
4019 h,
4020 minifb::WindowOptions {
4021 resize: false,
4022 scale: minifb::Scale::X1,
4023 ..Default::default()
4024 },
4025 )
4026 .map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
4027 apply_frame_pacing(&mut win, gfx.vsync);
4028 gfx.buffer = vec![0u32; w * h];
4029 gfx.width = w;
4030 gfx.height = h;
4031 gfx.window = Some(win);
4032 gfx.topmost_window = false;
4033 gfx.sync_projection();
4034 }
4035 #[cfg(target_arch = "wasm32")]
4036 {
4037 let mut gfx = self.gfx.borrow_mut();
4038 gfx.width = w;
4039 gfx.height = h;
4040 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4042 crate::gfx::webgl::resize(w as u32, h as u32);
4043 }
4044 return Ok(Value::Unit);
4045 },
4046
4047 "เติม" | "fill" | "gfx_fill" | "clear" | "填" | "塗り潰し" | "채우기" | "清"
4049 | "消去" | "지우기" | "پر_کن" | "املأ" | "מלא" | "بھرو" => {
4050 let r = self.arg_num(&args, 0, 0.0)? as u32;
4051 let g = self.arg_num(&args, 1, 0.0)? as u32;
4052 let b = self.arg_num(&args, 2, 0.0)? as u32;
4053 #[cfg(not(target_arch = "wasm32"))]
4054 {
4055 let c = (r << 16) | (g << 8) | b;
4056 let mut gfx = self.gfx.borrow_mut();
4057 gfx.buffer.fill(c);
4058 gfx.zbuf_needs_clear = true; gfx.edge_set.clear(); }
4061 #[cfg(target_arch = "wasm32")]
4062 {
4063 let mut gfx = self.gfx.borrow_mut();
4064 gfx.fill_r = r as f32 / 255.0;
4065 gfx.fill_g = g as f32 / 255.0;
4066 gfx.fill_b = b as f32 / 255.0;
4067 let c = (r << 16) | (g << 8) | b;
4068 gfx.buffer.fill(c);
4069 gfx.zbuf_needs_clear = true;
4070 gfx.edge_set.clear();
4071 }
4072 return Ok(Value::Unit);
4073 },
4074
4075 "set_color_hsl" | "颜色HSL" | "色相" | "HSL色" | "HSL색설정" | "สีHSLวาด" | "تنظیم_رنگ_HSL" | "عيّن_اللون_HSL" | "קבע_צבע_HSL" | "HSL_رنگ_مقرر_کرو" | "définir_couleur_hsl" | "farbe_hsl_setzen" | "задать_цвет_hsl" =>
4078 {
4079 let h = self.arg_num(&args, 0, 0.0)?;
4080 let s = self.arg_num(&args, 1, 70.0)?;
4081 let l = self.arg_num(&args, 2, 50.0)?;
4082 let hex = hsl_to_hex(h, s, l);
4083 let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
4084 let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
4085 let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
4086 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4087 return Ok(Value::Unit);
4088 },
4089
4090 "สีดินสอ" | "set_color" | "gfx_color" | "color" | "设色" | "色設定" | "색설정" | "تنظیم_رنگ" | "عيّن_اللون" | "קבע_צבע" | "رنگ_مقرر_کرو" =>
4092 {
4093 let r = self.arg_num(&args, 0, 255.0)? as u32;
4094 let g = self.arg_num(&args, 1, 255.0)? as u32;
4095 let b = self.arg_num(&args, 2, 255.0)? as u32;
4096 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4097 return Ok(Value::Unit);
4098 },
4099
4100 "วาดสามเหลี่ยม"
4102 | "draw_triangle"
4103 | "gfx_triangle"
4104 | "triangle"
4105 | "画三角"
4106 | "三角形描画"
4107 | "삼각형그리기" | "رسم_مثلث" | "ارسم_مثلثا" | "צייר_משולש" | "مثلث_کھینچو" => {
4108 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4109 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4110 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4111 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4112 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
4113 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
4114 let mut gfx = self.gfx.borrow_mut();
4115 let color = gfx.color;
4116 #[cfg(not(target_arch = "wasm32"))]
4117 {
4118 let w = gfx.width;
4119 let h = gfx.height;
4120 fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
4121 }
4122 #[cfg(target_arch = "wasm32")]
4123 gfx.depth_queue
4124 .push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
4125 return Ok(Value::Unit);
4126 },
4127
4128 "วาดเส้น" | "draw_line" | "gfx_line" | "line" | "画线" | "線描く" | "선그리기" | "رسم_خط" | "ارسم_خط" | "צייר_קו" | "لکیر_کھینچو" =>
4130 {
4131 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4132 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4133 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4134 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4135 let mut gfx = self.gfx.borrow_mut();
4136 let color = gfx.color;
4137 #[cfg(not(target_arch = "wasm32"))]
4138 {
4139 let w = gfx.width;
4140 let h = gfx.height;
4141 let aa = gfx.antialias;
4142 let add = gfx.blend == 1;
4143 if aa {
4144 crate::gfx::raster::draw_line_aa(
4145 &mut gfx.buffer,
4146 w,
4147 h,
4148 color,
4149 add,
4150 x0,
4151 y0,
4152 x1,
4153 y1,
4154 );
4155 } else {
4156 draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
4157 }
4158 }
4159 #[cfg(target_arch = "wasm32")]
4160 gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
4161 return Ok(Value::Unit);
4162 },
4163
4164 "วาดจุด" | "draw_pixel" | "gfx_pixel" | "pixel" | "画点" | "点描く" | "점그리기" | "رسم_نقطه" | "ارسم_نقطة" | "צייר_פיקסל" | "پکسل_کھینچو" =>
4166 {
4167 let px = self.arg_num(&args, 0, 0.0)? as i32;
4168 let py = self.arg_num(&args, 1, 0.0)? as i32;
4169 #[cfg(not(target_arch = "wasm32"))]
4170 {
4171 let mut gfx = self.gfx.borrow_mut();
4172 let color = gfx.color;
4173 let w = gfx.width;
4174 let h = gfx.height;
4175 if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
4176 gfx.buffer[py as usize * w + px as usize] = color;
4177 }
4178 }
4179 #[cfg(target_arch = "wasm32")]
4180 {
4181 let mut gfx = self.gfx.borrow_mut();
4183 let color = gfx.color;
4184 let x = px as f32;
4185 let y = py as f32;
4186 gfx.depth_queue
4187 .push_triangle(0.0, color, x, y, x + 1.0, y, x + 1.0, y + 1.0);
4188 gfx.depth_queue
4189 .push_triangle(0.0, color, x, y, x + 1.0, y + 1.0, x, y + 1.0);
4190 }
4191 return Ok(Value::Unit);
4192 },
4193
4194 "แสดงผล" | "present" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시" | "نمایش" | "اعرض" | "הצג" | "دکھاؤ" =>
4196 {
4197 #[cfg(not(target_arch = "wasm32"))]
4210 let pre_update_mouse_down = self
4211 .gfx
4212 .borrow()
4213 .window
4214 .as_ref()
4215 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
4216 .unwrap_or(false);
4217 #[cfg(not(target_arch = "wasm32"))]
4218 {
4219 ling_fps_tick();
4220 ling_phase_frame();
4221 {
4223 let mut gfx = self.gfx.borrow_mut();
4224 if !gfx.depth_queue.is_empty() {
4225 let w = gfx.width;
4226 let h = gfx.height;
4227 let dt = gfx.depth_test;
4228 let reset_z = gfx.zbuf_needs_clear;
4229 let (bm, ba) = (gfx.blend, gfx.alpha);
4230 let aa = gfx.antialias;
4231 let queue = std::mem::take(&mut gfx.depth_queue);
4232 {
4233 let g = &mut *gfx;
4234 let z = if dt { Some(&mut g.depth_buf) } else { None };
4235 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4236 }
4237 gfx.depth_queue.set_state(bm, ba);
4238 gfx.zbuf_needs_clear = false;
4239 }
4240 let _t = std::time::Instant::now();
4241 if !gfx.post_done {
4242 gfx.toon_post_process();
4243 }
4244 gfx.post_done = false;
4245 ling_phase_add(phase::TOON, _t.elapsed().as_nanos());
4246 let w = gfx.width;
4247 let h = gfx.height;
4248 let g = &mut *gfx;
4249 if g.frame_blur > 0.0 {
4250 let a = (g.frame_blur.clamp(0.0, 0.995) * 256.0) as u32;
4255 if g.prev_frame.len() != g.buffer.len() {
4256 g.prev_frame = g.buffer.clone();
4257 }
4258 for (dst, prev) in g.buffer.iter_mut().zip(g.prev_frame.iter_mut()) {
4259 let c = *dst;
4260 let pv = *prev;
4261 let pr = (((pv >> 16) & 0xFF) * a) >> 8;
4262 let pg = (((pv >> 8) & 0xFF) * a) >> 8;
4263 let pb = ((pv & 0xFF) * a) >> 8;
4264 let cr = (c >> 16) & 0xFF;
4265 let cg = (c >> 8) & 0xFF;
4266 let cb = c & 0xFF;
4267 let outp = (cr.max(pr) << 16) | (cg.max(pg) << 8) | cb.max(pb);
4268 *dst = outp;
4269 *prev = outp;
4270 }
4271 }
4272 if let Some(win) = g.window.as_mut() {
4273 let _b = std::time::Instant::now();
4274 win.update_with_buffer(&g.buffer, w, h)
4275 .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
4276 ling_phase_add(phase::BLIT, _b.elapsed().as_nanos());
4277 }
4278 }
4279 let mouse_pos = {
4281 let gfx = self.gfx.borrow();
4282 gfx.window
4283 .as_ref()
4284 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
4285 };
4286 let mut gfx = self.gfx.borrow_mut();
4287 if gfx.mouse_captured {
4288 let w = gfx.width as f32;
4289 let h = gfx.height as f32;
4290 if let Some((mx, my)) = mouse_pos {
4291 if gfx.last_mx.is_nan() {
4292 gfx.mouse_dx = 0.0;
4293 gfx.mouse_dy = 0.0;
4294 gfx.last_mx = mx;
4295 gfx.last_my = my;
4296 } else {
4297 gfx.mouse_dx = mx - gfx.last_mx;
4298 gfx.mouse_dy = my - gfx.last_my;
4299 let margin = 6.0;
4302 let (mut nx, mut ny, mut warp) = (mx, my, false);
4303 if mx < margin {
4304 nx = w - margin - 2.0;
4305 warp = true;
4306 } else if mx > w - margin {
4307 nx = margin + 2.0;
4308 warp = true;
4309 }
4310 if my < margin {
4311 ny = h - margin - 2.0;
4312 warp = true;
4313 } else if my > h - margin {
4314 ny = margin + 2.0;
4315 warp = true;
4316 }
4317 if warp {
4318 #[cfg(windows)]
4319 unsafe {
4320 #[repr(C)]
4321 struct RECT {
4322 left: i32,
4323 top: i32,
4324 right: i32,
4325 bottom: i32,
4326 }
4327 extern "system" {
4328 fn GetForegroundWindow() -> isize;
4329 fn GetWindowRect(hwnd: isize, lpRect: *mut RECT)
4330 -> i32;
4331 fn SetCursorPos(x: i32, y: i32) -> i32;
4332 }
4333 let hwnd = GetForegroundWindow();
4334 let mut rect =
4335 RECT { left: 0, top: 0, right: 0, bottom: 0 };
4336 if GetWindowRect(hwnd, &mut rect) != 0 {
4337 SetCursorPos(
4338 rect.left + nx as i32,
4339 rect.top + ny as i32,
4340 );
4341 }
4342 }
4343 gfx.last_mx = nx;
4344 gfx.last_my = ny;
4345 } else {
4346 gfx.last_mx = mx;
4347 gfx.last_my = my;
4348 }
4349 }
4350 } else {
4351 gfx.mouse_dx = 0.0;
4352 gfx.mouse_dy = 0.0;
4353 }
4354 } else if let Some((mx, my)) = mouse_pos {
4355 if gfx.last_mx.is_nan() {
4356 gfx.mouse_dx = 0.0;
4357 gfx.mouse_dy = 0.0;
4358 } else {
4359 gfx.mouse_dx = mx - gfx.last_mx;
4360 gfx.mouse_dy = my - gfx.last_my;
4361 }
4362 gfx.last_mx = mx;
4363 gfx.last_my = my;
4364 } else {
4365 gfx.mouse_dx = 0.0;
4366 gfx.mouse_dy = 0.0;
4367 }
4368
4369 let is_active = gfx.window.as_mut().map(|w| w.is_active()).unwrap_or(true);
4377 if is_active && !gfx.was_active {
4378 gfx.focus_grace_frames = 5;
4379 #[cfg(windows)]
4382 if gfx.topmost_window {
4383 if let Some(w) = gfx.window.as_ref() {
4384 set_window_topmost(w.get_window_handle() as isize, true);
4385 }
4386 }
4387 } else if !is_active && gfx.was_active {
4388 #[cfg(windows)]
4394 if gfx.topmost_window {
4395 if let Some(w) = gfx.window.as_ref() {
4396 set_window_topmost(w.get_window_handle() as isize, false);
4397 }
4398 }
4399 }
4400 gfx.was_active = is_active;
4401 if gfx.focus_grace_frames > 0 {
4402 gfx.focus_grace_frames -= 1;
4403 }
4404 }
4405 #[cfg(target_arch = "wasm32")]
4406 {
4407 {
4408 let mut gfx = self.gfx.borrow_mut();
4412 let w = gfx.width;
4413 let h = gfx.height;
4414 if gfx.buffer.len() != w * h {
4415 gfx.buffer.resize(w * h, 0);
4416 }
4417 if !gfx.depth_queue.is_empty() {
4418 let dt = gfx.depth_test;
4419 let reset_z = gfx.zbuf_needs_clear;
4420 let aa = gfx.antialias;
4421 let queue = std::mem::take(&mut gfx.depth_queue);
4422 {
4423 let g = &mut *gfx;
4424 let z = if dt { Some(&mut g.depth_buf) } else { None };
4425 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4426 }
4427 gfx.zbuf_needs_clear = false;
4428 }
4429 if !gfx.post_done {
4430 gfx.toon_post_process();
4431 }
4432 gfx.post_done = false;
4433 crate::gfx::webgl::blit_rgb(&gfx.buffer, w, h);
4434 }
4435 self.wasm_pace_frame();
4436 }
4437 #[cfg(not(target_arch = "wasm32"))]
4441 {
4442 self.mouse_was_down = pre_update_mouse_down;
4443 }
4444 self.frame_num += 1;
4446 return Ok(Value::Unit);
4447 },
4448
4449 "เปิดหน้าต่างเต็มจอ"
4451 | "open_fullscreen"
4452 | "fullscreen"
4453 | "全屏"
4454 | "全画面"
4455 | "전체화면" | "باز_کردن_تمامصفحه" | "افتح_ملء_الشاشة" | "פתח_מסך_מלא" | "فل_سکرین_کھولو" => {
4456 #[cfg(target_arch = "wasm32")]
4459 let (default_w, default_h) = {
4460 let (cw, ch) = crate::gfx::webgl::canvas_size();
4461 (cw as f64, ch as f64)
4462 };
4463 #[cfg(all(not(target_arch = "wasm32"), windows))]
4465 let (default_w, default_h) = unsafe {
4466 extern "system" {
4467 fn GetSystemMetrics(nIndex: i32) -> i32;
4468 }
4469 (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
4470 };
4471 #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
4472 let (default_w, default_h) = native_screen_size();
4473
4474 let w = args
4475 .get(1)
4476 .map(|v| self.to_number(v).unwrap_or(default_w) as usize)
4477 .unwrap_or(default_w as usize);
4478 let h = args
4479 .get(2)
4480 .map(|v| self.to_number(v).unwrap_or(default_h) as usize)
4481 .unwrap_or(default_h as usize);
4482 #[cfg(not(target_arch = "wasm32"))]
4483 {
4484 let title = args
4485 .first()
4486 .map(|v| v.to_string())
4487 .unwrap_or_else(|| "Ling".into());
4488 let mut gfx = self.gfx.borrow_mut();
4489 let mut win = minifb::Window::new(
4490 &title,
4491 w,
4492 h,
4493 minifb::WindowOptions {
4494 borderless: true,
4495 title: false,
4496 resize: false,
4497 topmost: true,
4498 scale: minifb::Scale::X1,
4499 ..Default::default()
4500 },
4501 )
4502 .map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
4503 apply_frame_pacing(&mut win, gfx.vsync);
4504 #[cfg(windows)]
4506 let hwnd = win.get_window_handle() as isize;
4507 gfx.buffer = vec![0u32; w * h];
4508 gfx.width = w;
4509 gfx.height = h;
4510 gfx.window = Some(win);
4511 gfx.topmost_window = true;
4512 #[cfg(windows)]
4513 {
4514 gfx.hwnd = hwnd;
4515 }
4516 gfx.sync_projection();
4517 #[cfg(windows)]
4519 make_borderless_fullscreen(hwnd, w as i32, h as i32);
4520 #[cfg(windows)]
4521 force_window_focus(hwnd);
4522 }
4523 #[cfg(target_arch = "wasm32")]
4524 {
4525 let mut gfx = self.gfx.borrow_mut();
4526 gfx.width = w;
4527 gfx.height = h;
4528 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4530 crate::gfx::webgl::resize(w as u32, h as u32);
4531 }
4532 return Ok(Value::Unit);
4533 },
4534
4535 "get_width" | "ความกว้าง" | "宽" | "幅取得" | "너비" | "عرض" | "العرض" | "רוחב" | "چوڑائی" | "obtenir_largeur" | "breite_abrufen" | "получить_ширину" => {
4537 return Ok(Value::Number(self.gfx.borrow().width as f64));
4538 },
4539 "get_height" | "ความสูง" | "高" | "高取得" | "높이" | "ارتفاع" | "الارتفاع" | "גובה" | "اونچائی" | "obtenir_hauteur" | "höhe_abrufen" | "получить_высоту" => {
4540 return Ok(Value::Number(self.gfx.borrow().height as f64));
4541 },
4542
4543 "monitor_width" | "screen_width" | "屏宽" | "画面幅" | "화면너비" | "ความกว้างจอ" | "عرض_مانیتور" | "عرض_الشاشة" | "רוחב_צג" | "مانیٹر_چوڑائی" | "largeur_moniteur" | "monitor_breite" | "ширина_монитора" =>
4546 {
4547 return Ok(Value::Number(monitor_info().0 as f64));
4548 },
4549 "monitor_height" | "screen_height" | "屏高" | "画面高" | "화면높이" | "ความสูงจอ" | "ارتفاع_مانیتور" | "ارتفاع_الشاشة" | "גובה_צג" | "مانیٹر_اونچائی" | "hauteur_moniteur" | "monitor_höhe" | "высота_монитора" =>
4551 {
4552 return Ok(Value::Number(monitor_info().1 as f64));
4553 },
4554 "monitor_refresh"
4556 | "monitor_hz"
4557 | "monitor_fps"
4558 | "refresh_rate"
4559 | "刷新率"
4560 | "リフレッシュレート"
4561 | "주사율"
4562 | "อัตรารีเฟรช" | "نرخ_بروزرسانی_مانیتور" | "معدل_تحديث_الشاشة" | "קצב_רענון_צג" | "مانیٹر_ریفریش_ریٹ" | "fréquence_moniteur" | "monitor_bildwiederholrate" | "частота_монитора" => {
4563 return Ok(Value::Number(monitor_info().2 as f64));
4564 },
4565 "monitor_info" | "screen_info" | "屏幕信息" | "画面情報" | "화면정보" | "ข้อมูลจอ" | "اطلاعات_مانیتور" | "معلومات_الشاشة" | "מידע_צג" | "مانیٹر_معلومات" | "info_moniteur" | "bildschirminfo" | "инфо_монитора" =>
4567 {
4568 let (w, h, hz) = monitor_info();
4569 return Ok(Value::List(Rc::new(vec![
4570 Value::Number(w as f64),
4571 Value::Number(h as f64),
4572 Value::Number(hz as f64),
4573 ])));
4574 },
4575 "set_fps"
4577 | "set_target_fps"
4578 | "target_fps"
4579 | "设帧率"
4580 | "フレームレート設定"
4581 | "프레임설정"
4582 | "ตั้งเฟรมเรต" | "تنظیم_نرخ_فریم" | "عيّن_معدل_الإطارات" | "קבע_קצב_פריימים" | "ایف_پی_ایس_مقرر_کرو" | "définir_fps" | "fps_setzen" | "задать_fps" => {
4583 #[cfg(not(target_arch = "wasm32"))]
4584 {
4585 let fps = self.arg_num(&args, 0, 60.0)?.max(1.0) as usize;
4586 let mut gfx = self.gfx.borrow_mut();
4587 if let Some(win) = gfx.window.as_mut() {
4588 win.set_target_fps(fps);
4589 }
4590 }
4591 #[cfg(target_arch = "wasm32")]
4592 {
4593 self.wasm_target_fps = self.arg_num(&args, 0, 60.0)?.max(1.0);
4594 self.wasm_next_present_ms = 0.0;
4595 }
4596 return Ok(Value::Unit);
4597 },
4598
4599 "set_vsync" | "vsync" | "垂直同步" | "垂直同期" | "수직동기" | "ตั้งวีซิงก์" | "تنظیم_ویسینک" | "عيّن_تزامن_رأسي" | "קבע_וי_סינק" | "وی_سینک_مقرر_کرو" | "définir_vsync" | "vsync_setzen" | "задать_vsync" =>
4603 {
4604 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4605 #[cfg(not(target_arch = "wasm32"))]
4606 {
4607 let mut gfx = self.gfx.borrow_mut();
4608 gfx.vsync = on;
4609 if let Some(win) = gfx.window.as_mut() {
4610 apply_frame_pacing(win, on);
4611 }
4612 }
4613 #[cfg(target_arch = "wasm32")]
4614 {
4615 self.wasm_target_fps = if on { monitor_info().2 as f64 } else { 240.0 };
4616 self.wasm_next_present_ms = 0.0;
4617 }
4618 return Ok(Value::Unit);
4619 },
4620
4621 "หน้าต่างเปิดอยู่"
4623 | "window_is_open"
4624 | "gfx_is_open"
4625 | "is_open"
4626 | "窗开"
4627 | "開いている"
4628 | "창열림" | "پنجره_باز_است" | "النافذة_مفتوحة" | "החלון_פתוח" | "ونڈو_کھلی_ہے" => {
4629 #[cfg(not(target_arch = "wasm32"))]
4630 {
4631 let gfx = self.gfx.borrow();
4632 if gfx.want_quit {
4633 return Ok(Value::Bool(false));
4634 }
4635 #[cfg(windows)]
4641 let escape_down = if gfx.topmost_window {
4642 window_is_foreground(gfx.hwnd) && os_key_down(0x1B) } else {
4644 gfx.window
4645 .as_ref()
4646 .map(|w| w.is_key_down(minifb::Key::Escape))
4647 .unwrap_or(false)
4648 };
4649 #[cfg(not(windows))]
4650 let escape_down = gfx
4651 .window
4652 .as_ref()
4653 .map(|w| w.is_key_down(minifb::Key::Escape))
4654 .unwrap_or(false);
4655 let open = gfx.window.as_ref().map(|w| w.is_open()).unwrap_or(false)
4656 && !escape_down;
4657 return Ok(Value::Bool(open));
4658 }
4659 #[cfg(target_arch = "wasm32")]
4660 return Ok(Value::Bool(true));
4661 },
4662
4663 "quit" | "exit_game" | "close_window" => {
4666 #[cfg(not(target_arch = "wasm32"))]
4667 {
4668 self.gfx.borrow_mut().want_quit = true;
4669 }
4670 return Ok(Value::Unit);
4671 },
4672
4673 "key_down" | "กดค้าง" | "按键" | "キー押す" | "키누름" | "کلید_فشرده" | "المفتاح_مضغوط" | "מקש_לחוץ" | "بٹن_دبا_ہوا" | "touche_enfoncée" | "taste_gedrückt" | "клавиша_нажата" => {
4675 #[cfg(not(target_arch = "wasm32"))]
4676 {
4677 let name = self.arg_str(&args, 0, "");
4678 let mut gfx = self.gfx.borrow_mut();
4679 #[cfg(windows)]
4687 if gfx.topmost_window {
4688 if !window_is_foreground(gfx.hwnd) {
4689 return Ok(Value::Bool(false));
4690 }
4691 return Ok(Value::Bool(
4692 str_to_vk(&name).map(os_key_down).unwrap_or(false),
4693 ));
4694 }
4695 if gfx.input_suppressed() {
4696 return Ok(Value::Bool(false));
4697 }
4698 let down = gfx
4699 .window
4700 .as_ref()
4701 .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
4702 .unwrap_or(false);
4703 return Ok(Value::Bool(down));
4704 }
4705 #[cfg(target_arch = "wasm32")]
4706 {
4707 let name = self.arg_str(&args, 0, "");
4708 return Ok(Value::Bool(crate::gfx::wasm_is_key_down(&name)));
4709 }
4710 },
4711
4712 "key_pressed" | "กดปุ่ม" | "键按" | "キー押した" | "키눌림" | "فشردن_کلید" | "ضغط_المفتاح" | "לחיצת_מקש" | "بٹن_دبانا" | "touche_appuyée" | "taste_getippt" | "клавиша_нажатие" => {
4714 #[cfg(not(target_arch = "wasm32"))]
4715 {
4716 let name = self.arg_str(&args, 0, "");
4717 let pressed = {
4718 let mut gfx = self.gfx.borrow_mut();
4719 #[cfg(windows)]
4720 let topmost = gfx.topmost_window;
4721 #[cfg(not(windows))]
4722 let topmost = false;
4723 if topmost {
4724 #[cfg(windows)]
4725 {
4726 if !window_is_foreground(gfx.hwnd) {
4727 false
4728 } else {
4729 match str_to_vk(&name) {
4730 Some(vk) => {
4731 let idx = (vk as usize) & 0xFF;
4732 let down = os_key_down(vk);
4733 let was = gfx.raw_keys_prev[idx];
4734 gfx.raw_keys_prev[idx] = down;
4735 down && !was
4736 },
4737 None => false,
4738 }
4739 }
4740 }
4741 #[cfg(not(windows))]
4742 {
4743 false
4744 }
4745 } else if gfx.input_suppressed() {
4746 false
4747 } else {
4748 gfx.window
4749 .as_ref()
4750 .and_then(|w| {
4751 str_to_minifb_key(&name)
4752 .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No))
4753 })
4754 .unwrap_or(false)
4755 }
4756 };
4757 let pressed =
4759 pressed || ((name == "enter" || name == "return") && gamepad::start_edge());
4760 return Ok(Value::Bool(pressed));
4761 }
4762 #[cfg(target_arch = "wasm32")]
4763 {
4764 let name = self.arg_str(&args, 0, "");
4765 let pressed = crate::gfx::wasm_is_key_pressed(&name);
4766 return Ok(Value::Bool(pressed));
4767 }
4768 },
4769
4770 "mouse_dx" | "เมาส์X" | "鼠ΔX" | "マウスΔX" | "마우스ΔX" | "دلتا_ماوس_ایکس" | "فارق_الفأرة_س" | "דלתא_עכבר_X" | "ماؤس_ڈیلٹا_ایکس" | "souris_dx" | "maus_dx" | "мышь_dx" => {
4772 #[cfg(not(target_arch = "wasm32"))]
4773 return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
4774 #[cfg(target_arch = "wasm32")]
4775 return Ok(Value::Number(crate::gfx::wasm_mouse_dx() as f64));
4776 },
4777 #[cfg(not(target_arch = "wasm32"))]
4779 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" | "غلتک_ماوس" | "عجلة_الفأرة" | "גלגלת_עכבר" | "ماؤس_اسکرول" =>
4780 {
4781 let gfx = self.gfx.borrow();
4782 let s = gfx
4783 .window
4784 .as_ref()
4785 .and_then(|w| w.get_scroll_wheel())
4786 .map(|(_, y)| y as f64)
4787 .unwrap_or(0.0);
4788 return Ok(Value::Number(s));
4789 },
4790 #[cfg(target_arch = "wasm32")]
4791 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" | "غلتک_ماوس" | "عجلة_الفأرة" | "גלגלת_עכבר" | "ماؤس_اسکرول" =>
4792 {
4793 return Ok(Value::Number(0.0));
4794 },
4795 "mouse_dy" | "เมาส์Y" | "鼠ΔY" | "マウスΔY" | "마우스ΔY" | "دلتا_ماوس_ایگرگ" | "فارق_الفأرة_ص" | "דלתא_עכבר_Y" | "ماؤس_ڈیلٹا_وائی" | "souris_dy" | "maus_dy" | "мышь_dy" => {
4796 #[cfg(not(target_arch = "wasm32"))]
4797 return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
4798 #[cfg(target_arch = "wasm32")]
4799 return Ok(Value::Number(crate::gfx::wasm_mouse_dy() as f64));
4800 },
4801
4802 "pad_poll" | "手柄轮询" | "パッド更新" | "패드폴링" | "อัปเดตแพด" | "بررسی_دسته" | "استطلع_اليد" | "בדוק_בקר" | "گیم_پیڈ_پول" | "interroger_manette" | "gamepad_abfragen" | "опросить_геймпад" =>
4805 {
4806 #[cfg(not(target_arch = "wasm32"))]
4807 return Ok(Value::Number(self.pad_poll() as f64));
4808 #[cfg(target_arch = "wasm32")]
4809 return Ok(Value::Number(input_web::poll() as f64));
4810 },
4811 "pad_count" | "手柄数" | "パッド数" | "패드수" | "จำนวนแพด" | "تعداد_دسته" | "عدد_أيدي_التحكم" | "מספר_בקרים" | "گیم_پیڈ_تعداد" | "nombre_manettes" | "gamepad_anzahl" | "число_геймпадов" =>
4813 {
4814 #[cfg(not(target_arch = "wasm32"))]
4815 {
4816 let inp = self.input.borrow();
4817 let n = inp.as_ref().map_or(0, |s| s.sensorium.devices.count());
4818 return Ok(Value::Number(n as f64));
4819 }
4820 #[cfg(target_arch = "wasm32")]
4821 return Ok(Value::Number(input_web::count() as f64));
4822 },
4823 "pad_connected" | "手柄连接" | "パッド接続" | "패드연결" | "แพดเชื่อม" | "دسته_متصل" | "يد_التحكم_متصلة" | "בקר_מחובר" | "گیم_پیڈ_منسلک" | "manette_connectée" | "gamepad_verbunden" | "геймпад_подключён" =>
4825 {
4826 #[cfg(not(target_arch = "wasm32"))]
4827 {
4828 let i = self.arg_num(&args, 0, 0.0)? as usize;
4829 let inp = self.input.borrow();
4830 let c = inp
4831 .as_ref()
4832 .is_some_and(|s| s.sensorium.devices.for_player(i as u8).is_some());
4833 return Ok(Value::Bool(c));
4834 }
4835 #[cfg(target_arch = "wasm32")]
4836 {
4837 let i = self.arg_num(&args, 0, 0.0)? as usize;
4838 return Ok(Value::Bool(input_web::is_connected(i)));
4839 }
4840 },
4841 "pad_button" | "手柄按键" | "パッドボタン" | "패드버튼" | "ปุ่มแพด" | "دکمه_دسته" | "زر_اليد" | "כפתור_בקר" | "گیم_پیڈ_بٹن" | "bouton_manette" | "gamepad_taste" | "кнопка_геймпада" =>
4843 {
4844 #[cfg(not(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 let down = parse_pad_button(&name)
4849 .is_some_and(|b| self.with_pad(i, false, |p| p.is_down(b)));
4850 return Ok(Value::Bool(down));
4851 }
4852 #[cfg(target_arch = "wasm32")]
4853 {
4854 let i = self.arg_num(&args, 0, 0.0)? as usize;
4855 let name = self.arg_str(&args, 1, "");
4856 return Ok(Value::Bool(input_web::button_down(i, &name)));
4857 }
4858 },
4859 "pad_pressed" | "手柄按下" | "パッド押下" | "패드눌림" | "แพดกด" | "دکمه_دسته_فشرده" | "زر_اليد_مضغوط" | "כפתור_בקר_לחוץ" | "گیم_پیڈ_دبایا" | "manette_appuyée" | "gamepad_gedrückt" | "геймпад_нажат" =>
4862 {
4863 #[cfg(not(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 let p = parse_pad_button(&name)
4868 .is_some_and(|b| self.with_pad(i, false, |g| g.just_pressed(b)));
4869 return Ok(Value::Bool(p));
4870 }
4871 #[cfg(target_arch = "wasm32")]
4872 {
4873 let i = self.arg_num(&args, 0, 0.0)? as usize;
4874 let name = self.arg_str(&args, 1, "");
4875 return Ok(Value::Bool(input_web::button_down(i, &name)));
4876 }
4877 },
4878 "pad_lx" | "手柄左X" | "パッド左X" | "패드왼X" | "แพดซ้ายX" | "آنالوگ_چپ_ایکس" | "عصا_اليسرى_س" | "ג'ויסטיק_שמאל_X" | "بائیں_اسٹک_ایکس" | "manette_axe_gauche_x" | "gamepad_lx" | "геймпад_ось_лево_x" => {
4880 #[cfg(not(target_arch = "wasm32"))]
4881 {
4882 let i = self.arg_num(&args, 0, 0.0)? as usize;
4883 return Ok(Value::Number(
4884 self.with_pad(i, 0.0, |p| p.left_stick.x as f64),
4885 ));
4886 }
4887 #[cfg(target_arch = "wasm32")]
4888 {
4889 let i = self.arg_num(&args, 0, 0.0)? as usize;
4890 return Ok(Value::Number(input_web::axis_lx(i) as f64));
4891 }
4892 },
4893 "pad_ly" | "手柄左Y" | "パッド左Y" | "패드왼Y" | "แพดซ้ายY" | "آنالوگ_چپ_ایگرگ" | "عصا_اليسرى_ص" | "ג'ויסטיק_שמאל_Y" | "بائیں_اسٹک_وائی" | "manette_axe_gauche_y" | "gamepad_ly" | "геймпад_ось_лево_y" => {
4894 #[cfg(not(target_arch = "wasm32"))]
4895 {
4896 let i = self.arg_num(&args, 0, 0.0)? as usize;
4897 return Ok(Value::Number(
4898 self.with_pad(i, 0.0, |p| p.left_stick.y as f64),
4899 ));
4900 }
4901 #[cfg(target_arch = "wasm32")]
4902 {
4903 let i = self.arg_num(&args, 0, 0.0)? as usize;
4904 return Ok(Value::Number(input_web::axis_ly(i) as f64));
4905 }
4906 },
4907 "pad_rx" | "手柄右X" | "パッド右X" | "패드오X" | "แพดขวาX" | "آنالوگ_راست_ایکس" | "عصا_اليمنى_س" | "ג'ויסטיק_ימין_X" | "دائیں_اسٹک_ایکس" | "manette_axe_droit_x" | "gamepad_rx" | "геймпад_ось_право_x" => {
4908 #[cfg(not(target_arch = "wasm32"))]
4909 {
4910 let i = self.arg_num(&args, 0, 0.0)? as usize;
4911 return Ok(Value::Number(
4912 self.with_pad(i, 0.0, |p| p.right_stick.x as f64),
4913 ));
4914 }
4915 #[cfg(target_arch = "wasm32")]
4916 {
4917 let i = self.arg_num(&args, 0, 0.0)? as usize;
4918 return Ok(Value::Number(input_web::axis_rx(i) as f64));
4919 }
4920 },
4921 "pad_ry" | "手柄右Y" | "パッド右Y" | "패드오Y" | "แพดขวาY" | "آنالوگ_راست_ایگرگ" | "عصا_اليمنى_ص" | "ג'ויסטיק_ימין_Y" | "دائیں_اسٹک_وائی" | "manette_axe_droit_y" | "gamepad_ry" | "геймпад_ось_право_y" => {
4922 #[cfg(not(target_arch = "wasm32"))]
4923 {
4924 let i = self.arg_num(&args, 0, 0.0)? as usize;
4925 return Ok(Value::Number(
4926 self.with_pad(i, 0.0, |p| p.right_stick.y as f64),
4927 ));
4928 }
4929 #[cfg(target_arch = "wasm32")]
4930 {
4931 let i = self.arg_num(&args, 0, 0.0)? as usize;
4932 return Ok(Value::Number(input_web::axis_ry(i) as f64));
4933 }
4934 },
4935 "pad_lt" | "手柄左扳机" | "パッド左トリガー" | "패드왼트리거" | "ไกแพดซ้าย" | "ماشه_چپ" | "زناد_اليسار" | "הדק_שמאל" | "بائیں_ٹریگر" | "manette_gâchette_gauche" | "gamepad_lt" | "геймпад_триггер_лево" =>
4937 {
4938 #[cfg(not(target_arch = "wasm32"))]
4939 {
4940 let i = self.arg_num(&args, 0, 0.0)? as usize;
4941 return Ok(Value::Number(
4942 self.with_pad(i, 0.0, |p| p.left_trigger as f64),
4943 ));
4944 }
4945 #[cfg(target_arch = "wasm32")]
4946 {
4947 let i = self.arg_num(&args, 0, 0.0)? as usize;
4948 return Ok(Value::Number(input_web::trigger_lt(i) as f64));
4949 }
4950 },
4951 "pad_rt" | "手柄右扳机" | "パッド右トリガー" | "패드오트리거" | "ไกแพดขวา" | "ماشه_راست" | "زناد_اليمين" | "הדק_ימין" | "دائیں_ٹریگر" | "manette_gâchette_droite" | "gamepad_rt" | "геймпад_триггер_право" =>
4952 {
4953 #[cfg(not(target_arch = "wasm32"))]
4954 {
4955 let i = self.arg_num(&args, 0, 0.0)? as usize;
4956 return Ok(Value::Number(
4957 self.with_pad(i, 0.0, |p| p.right_trigger as f64),
4958 ));
4959 }
4960 #[cfg(target_arch = "wasm32")]
4961 {
4962 let i = self.arg_num(&args, 0, 0.0)? as usize;
4963 return Ok(Value::Number(input_web::trigger_rt(i) as f64));
4964 }
4965 },
4966 "pad_rumble" | "手柄震动" | "パッド振動" | "패드진동" | "แพดสั่น" | "لرزش_دسته" | "اهتزاز_اليد" | "רטט_בקר" | "گیم_پیڈ_تھرتھراہٹ" | "vibration_manette" | "gamepad_vibration" | "вибрация_геймпада" =>
4968 {
4969 #[cfg(not(target_arch = "wasm32"))]
4970 {
4971 use ling_input::backend::InputBackend;
4972 let i = self.arg_num(&args, 0, 0.0)? as usize;
4973 let lo = self.arg_num(&args, 1, 0.0)? as f32;
4974 let hi = self.arg_num(&args, 2, lo as f64)? as f32;
4975 let mut inp = self.input.borrow_mut();
4976 if let Some(s) = inp.as_mut() {
4977 if let Some(dev) = s.sensorium.devices.for_player(i as u8).map(|d| d.id) {
4978 s.backend.set_rumble(
4979 dev,
4980 ling_input::Rumble { low: lo, high: hi, ..Default::default() },
4981 );
4982 }
4983 }
4984 return Ok(Value::Unit);
4985 }
4986 #[cfg(target_arch = "wasm32")]
4987 return Ok(Value::Unit);
4988 },
4989
4990 "set_camera_pos" | "ตั้งตำแหน่งกล้อง" | "镜坐标" | "カメラ座標" | "카메라좌표" | "تنظیم_موقعیت_دوربین" | "عيّن_موضع_الكاميرا" | "קבע_מיקום_מצלמה" | "کیمرہ_مقام_مقرر_کرو" | "définir_position_caméra" | "kameraposition_setzen" | "задать_позицию_камеры" =>
4992 {
4993 let x = self.arg_num(&args, 0, 0.0)? as f32;
4994 let y = self.arg_num(&args, 1, 0.0)? as f32;
4995 let z = self.arg_num(&args, 2, 0.0)? as f32;
4996 {
4997 let mut gfx = self.gfx.borrow_mut();
4998 gfx.camera.tx = x;
4999 gfx.camera.ty = y;
5000 gfx.camera.tz = z;
5001 }
5002 #[cfg(not(target_arch = "wasm32"))]
5003 if let Some(audio) = &self.audio {
5004 audio.set_listener_pos(x, y, z);
5005 }
5006 return Ok(Value::Unit);
5007 },
5008
5009 "move_camera" => {
5011 let dx = self.arg_num(&args, 0, 0.0)? as f32;
5012 let dy = self.arg_num(&args, 1, 0.0)? as f32;
5013 let dz = self.arg_num(&args, 2, 0.0)? as f32;
5014 let mut gfx = self.gfx.borrow_mut();
5015 gfx.camera.tx += dx;
5016 gfx.camera.ty += dy;
5017 gfx.camera.tz += dz;
5018 return Ok(Value::Unit);
5019 },
5020
5021 "set_zdist" | "ตั้งระยะห่าง" | "镜距" | "Z距離設定" | "Z거리설정" | "تنظیم_فاصله_عمق" | "عيّن_مسافة_العمق" | "קבע_מרחק_עומק" | "گہرائی_فاصلہ_مقرر_کرو" | "définir_distance_z" | "z_abstand_setzen" | "задать_дистанцию_z" =>
5023 {
5024 let d = self.arg_num(&args, 0, 5.0)? as f32;
5025 self.gfx.borrow_mut().camera.zdist = d;
5026 return Ok(Value::Unit);
5027 },
5028
5029 "capture_mouse" | "จับเมาส์" | "捕鼠" | "マウス捕捉" | "마우스잡기" | "ضبط_ماوس" | "امسك_الفأرة" | "לכוד_עכבר" | "ماؤس_پکڑو" | "capturer_souris" | "maus_erfassen" | "захватить_мышь" =>
5031 {
5032 #[cfg(not(target_arch = "wasm32"))]
5033 {
5034 let mut gfx = self.gfx.borrow_mut();
5035 gfx.mouse_captured = true;
5036 gfx.last_mx = f32::NAN;
5037 if let Some(win) = gfx.window.as_mut() {
5038 win.set_cursor_visibility(false);
5039 }
5040 }
5041 return Ok(Value::Unit);
5042 },
5043
5044 "release_mouse" => {
5046 #[cfg(not(target_arch = "wasm32"))]
5047 {
5048 let mut gfx = self.gfx.borrow_mut();
5049 gfx.mouse_captured = false;
5050 gfx.last_mx = f32::NAN;
5051 if let Some(win) = gfx.window.as_mut() {
5052 win.set_cursor_visibility(true);
5053 }
5054 #[cfg(windows)]
5055 unsafe {
5056 extern "system" {
5058 fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
5059 }
5060 ClipCursor(std::ptr::null());
5061 }
5062 }
5063 return Ok(Value::Unit);
5064 },
5065
5066 "cursor_hide" => {
5072 #[cfg(not(target_arch = "wasm32"))]
5073 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5074 win.set_cursor_visibility(false);
5075 }
5076 return Ok(Value::Unit);
5077 },
5078 "cursor_show" => {
5079 #[cfg(not(target_arch = "wasm32"))]
5080 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5081 win.set_cursor_visibility(true);
5082 }
5083 return Ok(Value::Unit);
5084 },
5085
5086 "set_camera" | "ตั้งกล้อง" | "设镜" | "设置摄像机" | "カメラ設定" | "카메라설정" | "تنظیم_دوربین" | "عيّن_الكاميرا" | "קבע_מצלמה" | "کیمرہ_مقرر_کرو" | "définir_caméra" | "kamera_setzen" | "задать_камеру" =>
5093 {
5094 let cry = self.arg_num(&args, 0, 1.0)? as f32;
5095 let sry = self.arg_num(&args, 1, 0.0)? as f32;
5096 let crx = self.arg_num(&args, 2, 1.0)? as f32;
5097 let srx = self.arg_num(&args, 3, 0.0)? as f32;
5098 let mut gfx = self.gfx.borrow_mut();
5099 gfx.camera.cry = cry;
5100 gfx.camera.sry = sry;
5101 gfx.camera.crx = crx;
5102 gfx.camera.srx = srx;
5103 return Ok(Value::Unit);
5104 },
5105
5106 "set_projection" | "ตั้งโปรเจกชัน" | "投影" | "投影設定" | "투영설정" | "تنظیم_فرافکنی" | "عيّن_الإسقاط" | "קבע_הטלה" | "پروجیکشن_مقرر_کرو" | "définir_projection" | "projektion_setzen" | "задать_проекцию" =>
5109 {
5110 let cx = self.arg_num(&args, 0, 960.0)? as f32;
5111 let cy = self.arg_num(&args, 1, 540.0)? as f32;
5112 let focal = self.arg_num(&args, 2, 1080.0)? as f32;
5113 let zdist = self.arg_num(&args, 3, 5.0)? as f32;
5114 let mut gfx = self.gfx.borrow_mut();
5115 gfx.camera.cx = cx;
5116 gfx.camera.cy = cy;
5117 gfx.camera.focal = focal;
5118 gfx.camera.zdist = zdist;
5119 return Ok(Value::Unit);
5120 },
5121
5122 "gltf_load" => {
5124 let path = self.arg_str(&args, 0, "");
5125 match ling_physics::gltf::GltfModel::load(&path) {
5126 Ok(m) => {
5127 self.gltf_models.borrow_mut().push(m);
5128 let h = self.gltf_models.borrow().len() - 1;
5129 return Ok(Value::Number(h as f64));
5130 }
5131 Err(e) => {
5132 eprintln!("mesh_load failed ({path}): {e}");
5133 return Ok(Value::Number(-1.0));
5134 }
5135 }
5136 },
5137 "gltf_anim_count" => {
5139 let h = self.arg_num(&args, 0, -1.0)? as i64;
5140 let n = self
5141 .gltf_models
5142 .borrow()
5143 .get(h as usize)
5144 .map(|m| m.animations.len())
5145 .unwrap_or(0);
5146 return Ok(Value::Number(n as f64));
5147 },
5148 "gltf_anim_name" => {
5150 let h = self.arg_num(&args, 0, -1.0)? as i64;
5151 let i = self.arg_num(&args, 1, 0.0)? as usize;
5152 let s = self
5153 .gltf_models
5154 .borrow()
5155 .get(h as usize)
5156 .and_then(|m| m.animations.get(i))
5157 .map(|a| a.name.clone())
5158 .unwrap_or_default();
5159 return Ok(Value::Str(s));
5160 },
5161 "gltf_anim_dur" => {
5163 let h = self.arg_num(&args, 0, -1.0)? as i64;
5164 let i = self.arg_num(&args, 1, 0.0)? as usize;
5165 let d = self
5166 .gltf_models
5167 .borrow()
5168 .get(h as usize)
5169 .and_then(|m| m.animations.get(i))
5170 .map(|a| a.duration)
5171 .unwrap_or(0.0);
5172 return Ok(Value::Number(d as f64));
5173 },
5174 "gltf_tris" => {
5176 let h = self.arg_num(&args, 0, -1.0)? as i64;
5177 let n: usize = self
5178 .gltf_models
5179 .borrow()
5180 .get(h as usize)
5181 .map(|m| m.meshes.iter().map(|mm| mm.indices.len()).sum::<usize>() / 3)
5182 .unwrap_or(0);
5183 return Ok(Value::Number(n as f64));
5184 },
5185
5186 "gltf_joint_count" => {
5188 let h = self.arg_num(&args, 0, -1.0)? as i64;
5189 let n = self
5190 .gltf_models
5191 .borrow()
5192 .get(h as usize)
5193 .and_then(|m| m.skins.first())
5194 .map(|s| s.joints.len())
5195 .unwrap_or(0);
5196 return Ok(Value::Number(n as f64));
5197 },
5198 "gltf_joint_name" => {
5200 let h = self.arg_num(&args, 0, -1.0)? as i64;
5201 let j = self.arg_num(&args, 1, 0.0)? as usize;
5202 let models = self.gltf_models.borrow();
5203 let s = models
5204 .get(h as usize)
5205 .and_then(|m| {
5206 m.skins
5207 .first()
5208 .and_then(|sk| sk.joints.get(j))
5209 .and_then(|jt| m.nodes.get(jt.node_idx))
5210 .map(|n| n.name.clone())
5211 })
5212 .unwrap_or_default();
5213 return Ok(Value::Str(s));
5214 },
5215
5216 "gltf_draw" => {
5221 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5222 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5223 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5224 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5225 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5226 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5227 let (sy, cyy) = yaw.sin_cos();
5228 let models = self.gltf_models.borrow();
5229 let model = match models.get(hh as usize) {
5230 Some(m) => m,
5231 None => return Ok(Value::Unit),
5232 };
5233 let mut gfx = self.gfx.borrow_mut();
5234 let cp = {
5235 let c = &gfx.camera;
5236 ling_gpu::CameraParams {
5237 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5238 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5239 tx: c.tx, ty: c.ty, tz: c.tz,
5240 }
5241 };
5242 let near = -gfx.camera.zdist + 0.02;
5243 let ambient = gfx.ambient;
5244 for mesh in &model.meshes {
5245 let nlow = mesh.name.to_lowercase();
5246 let base: u32 = if nlow.contains("hair") {
5247 0x7a4a28
5248 } else if nlow.contains("cloth") || nlow.contains("top") {
5249 0x4a86e0
5250 } else if nlow.contains("wing") {
5251 0xe6ecf5
5252 } else if nlow.contains("star") {
5253 0xffd24d
5254 } else {
5255 0xf2d6b8
5256 };
5257 let nv = mesh.verts.len();
5258 if nv == 0 {
5259 continue;
5260 }
5261 let mut world = vec![0.0f32; nv * 3];
5262 for (i, v) in mesh.verts.iter().enumerate() {
5263 let gx = v.pos.x * scale;
5264 let gy = -v.pos.y * scale;
5265 let gz = -v.pos.z * scale;
5266 let rx = gx * cyy + gz * sy;
5267 let rz = -gx * sy + gz * cyy;
5268 world[i * 3] = ox + rx;
5269 world[i * 3 + 1] = oy + gy;
5270 world[i * 3 + 2] = oz + rz;
5271 }
5272 let mut proj = vec![0.0f32; nv * 3];
5273 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5274 let idx = &mesh.indices;
5275 let nt = idx.len() / 3;
5276 for t in 0..nt {
5277 let ia = idx[t * 3] as usize;
5278 let ib = idx[t * 3 + 1] as usize;
5279 let ic = idx[t * 3 + 2] as usize;
5280 if ia >= nv || ib >= nv || ic >= nv {
5281 continue;
5282 }
5283 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5284 if (da + db + dc) / 3.0 <= near {
5285 continue;
5286 }
5287 let col = {
5288 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5289 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5290 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5291 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5292 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5293 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5294 let centroid =
5295 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5296 if gfx.flat_shade {
5297 base
5298 } else {
5299 crate::gfx::light::compute_lit_color(
5300 base, normal, centroid, &gfx.lights, ambient,
5301 )
5302 }
5303 };
5304 let depth = (da + db + dc) / 3.0;
5305 let col = gfx.fog_apply(col, depth);
5306 gfx.depth_queue.push_triangle_zv(
5307 col,
5308 proj[ia * 3], proj[ia * 3 + 1], da,
5309 proj[ib * 3], proj[ib * 3 + 1], db,
5310 proj[ic * 3], proj[ic * 3 + 1], dc,
5311 );
5312 }
5313 }
5314 return Ok(Value::Unit);
5315 },
5316
5317 "gltf_autorig" => {
5319 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5320 let mut models = self.gltf_models.borrow_mut();
5321 if let Some(m) = models.get_mut(hh as usize) {
5322 return Ok(Value::Number(m.autorig() as f64));
5323 }
5324 return Ok(Value::Number(0.0));
5325 },
5326
5327 "gltf_pose_draw" => {
5331 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5332 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5333 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5334 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5335 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5336 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5337 let euler: Vec<f32> = match args.get(6) {
5338 Some(Value::List(v)) => {
5339 v.iter().map(|x| self.to_number(x).unwrap_or(0.0) as f32).collect()
5340 },
5341 _ => Vec::new(),
5342 };
5343 let (sy, cyy) = yaw.sin_cos();
5344 let models = self.gltf_models.borrow();
5345 let model = match models.get(hh as usize) {
5346 Some(m) => m,
5347 None => return Ok(Value::Unit),
5348 };
5349 let skinned = model.skin_local(&euler);
5350 let mut gfx = self.gfx.borrow_mut();
5351 let cp = {
5352 let c = &gfx.camera;
5353 ling_gpu::CameraParams {
5354 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5355 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5356 tx: c.tx, ty: c.ty, tz: c.tz,
5357 }
5358 };
5359 let near = -gfx.camera.zdist + 0.02;
5360 let ambient = gfx.ambient;
5361 for (mi, mesh) in model.meshes.iter().enumerate() {
5362 let nlow = mesh.name.to_lowercase();
5363 let base: u32 = if nlow.contains("hair") {
5364 0x7a4a28
5365 } else if nlow.contains("cloth") || nlow.contains("top") {
5366 0x4a86e0
5367 } else if nlow.contains("wing") {
5368 0xe6ecf5
5369 } else if nlow.contains("star") {
5370 0xffd24d
5371 } else {
5372 0xf2d6b8
5373 };
5374 let sk = match skinned.get(mi) {
5375 Some(s) => s,
5376 None => continue,
5377 };
5378 let nv = sk.len();
5379 if nv == 0 {
5380 continue;
5381 }
5382 let mut world = vec![0.0f32; nv * 3];
5383 for i in 0..nv {
5384 let gx = sk[i][0] * scale;
5385 let gy = -sk[i][1] * scale;
5386 let gz = -sk[i][2] * scale;
5387 let rx = gx * cyy + gz * sy;
5388 let rz = -gx * sy + gz * cyy;
5389 world[i * 3] = ox + rx;
5390 world[i * 3 + 1] = oy + gy;
5391 world[i * 3 + 2] = oz + rz;
5392 }
5393 let mut proj = vec![0.0f32; nv * 3];
5394 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5395 let idx = &mesh.indices;
5396 let nt = idx.len() / 3;
5397 for t in 0..nt {
5398 let ia = idx[t * 3] as usize;
5399 let ib = idx[t * 3 + 1] as usize;
5400 let ic = idx[t * 3 + 2] as usize;
5401 if ia >= nv || ib >= nv || ic >= nv {
5402 continue;
5403 }
5404 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5405 if (da + db + dc) / 3.0 <= near {
5406 continue;
5407 }
5408 let col = {
5409 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5410 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5411 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5412 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5413 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5414 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5415 let centroid =
5416 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5417 if gfx.flat_shade {
5418 base
5419 } else {
5420 crate::gfx::light::compute_lit_color(
5421 base, normal, centroid, &gfx.lights, ambient,
5422 )
5423 }
5424 };
5425 let depth = (da + db + dc) / 3.0;
5426 let col = gfx.fog_apply(col, depth);
5427 gfx.depth_queue.push_triangle_zv(
5428 col,
5429 proj[ia * 3], proj[ia * 3 + 1], da,
5430 proj[ib * 3], proj[ib * 3 + 1], db,
5431 proj[ic * 3], proj[ic * 3 + 1], dc,
5432 );
5433 }
5434 }
5435 return Ok(Value::Unit);
5436 },
5437
5438 "draw_mesh" | "วาดเมช" | "رسم_مش" | "ارسم_شبكة" | "צייר_רשת" | "میش_کھینچو" | "dessiner_maillage" | "netz_zeichnen" | "рисовать_меш" => {
5445 let pos = match args.first() {
5446 Some(Value::List(v)) => v,
5447 _ => return Ok(Value::Unit),
5448 };
5449 let idx = match args.get(1) {
5450 Some(Value::List(v)) => v,
5451 _ => return Ok(Value::Unit),
5452 };
5453 let ox = self.arg_num(&args, 2, 0.0)? as f32;
5454 let oy = self.arg_num(&args, 3, 0.0)? as f32;
5455 let oz = self.arg_num(&args, 4, 0.0)? as f32;
5456 let scale = self.arg_num(&args, 5, 1.0)? as f32;
5457 let mode = self.arg_num(&args, 6, 0.0)? as i64;
5458 let nv = pos.len() / 3;
5459 if nv == 0 {
5460 return Ok(Value::Unit);
5461 }
5462 let mut world = vec![0.0f32; nv * 3];
5463 for i in 0..nv {
5464 world[i * 3] = ox + self.to_number(&pos[i * 3]).unwrap_or(0.0) as f32 * scale;
5465 world[i * 3 + 1] =
5466 oy + self.to_number(&pos[i * 3 + 1]).unwrap_or(0.0) as f32 * scale;
5467 world[i * 3 + 2] =
5468 oz + self.to_number(&pos[i * 3 + 2]).unwrap_or(0.0) as f32 * scale;
5469 }
5470 let mut gfx = self.gfx.borrow_mut();
5471 let cp = {
5472 let c = &gfx.camera;
5473 ling_gpu::CameraParams {
5474 cry: c.cry,
5475 sry: c.sry,
5476 crx: c.crx,
5477 srx: c.srx,
5478 cx: c.cx,
5479 cy: c.cy,
5480 focal: c.focal,
5481 zdist: c.zdist,
5482 tx: c.tx,
5483 ty: c.ty,
5484 tz: c.tz,
5485 }
5486 };
5487 let near = -gfx.camera.zdist + 0.02;
5488 let base = gfx.color;
5489 let ambient = gfx.ambient;
5490 let mut proj = vec![0.0f32; nv * 3]; ling_gpu::backend().project_points(&world, &cp, &mut proj);
5492 let nt = idx.len() / 3;
5493 for t in 0..nt {
5494 let ia = self.to_number(&idx[t * 3]).unwrap_or(0.0) as usize;
5495 let ib = self.to_number(&idx[t * 3 + 1]).unwrap_or(0.0) as usize;
5496 let ic = self.to_number(&idx[t * 3 + 2]).unwrap_or(0.0) as usize;
5497 if ia >= nv || ib >= nv || ic >= nv {
5498 continue;
5499 }
5500 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5501 if (da + db + dc) / 3.0 <= near {
5502 continue;
5503 } let col = if mode == 1 {
5505 let h = t as f32 * 0.6;
5506 let r = ((h.sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5507 let g = (((h + 2.094).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5508 let b = (((h + 4.189).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5509 (r << 16) | (g << 8) | b
5510 } else {
5511 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5512 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5513 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5514 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5515 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5516 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5517 let centroid = [
5518 (ax + bx + px) / 3.0,
5519 (ay + by + py) / 3.0,
5520 (az + bz + pz) / 3.0,
5521 ];
5522 if gfx.flat_shade {
5523 base
5524 } else {
5525 crate::gfx::light::compute_lit_color(
5526 base,
5527 normal,
5528 centroid,
5529 &gfx.lights,
5530 ambient,
5531 )
5532 }
5533 };
5534 let depth = (da + db + dc) / 3.0;
5535 let col = gfx.fog_apply(col, depth);
5536 gfx.depth_queue.push_triangle_zv(
5540 col,
5541 proj[ia * 3],
5542 proj[ia * 3 + 1],
5543 da,
5544 proj[ib * 3],
5545 proj[ib * 3 + 1],
5546 db,
5547 proj[ic * 3],
5548 proj[ic * 3 + 1],
5549 dc,
5550 );
5551 }
5552 return Ok(Value::Unit);
5553 },
5554
5555 "add_light" | "เพิ่มแสง" | "加灯" | "ライト追加" | "조명추가" | "افزودن_نور" | "أضف_ضوء" | "הוסף_אור" | "روشنی_شامل_کرو" | "ajouter_lumière" | "licht_hinzufügen" | "добавить_свет" =>
5559 {
5560 let x = self.arg_num(&args, 0, 0.0)? as f32;
5561 let y = self.arg_num(&args, 1, -3.0)? as f32;
5562 let z = self.arg_num(&args, 2, 3.0)? as f32;
5563 let mut r = self.arg_num(&args, 3, 1.0)? as f32;
5564 let mut g = self.arg_num(&args, 4, 1.0)? as f32;
5565 let mut b = self.arg_num(&args, 5, 1.0)? as f32;
5566 if r > 1.5 || g > 1.5 || b > 1.5 {
5569 r /= 255.0;
5570 g /= 255.0;
5571 b /= 255.0;
5572 }
5573 let intensity = self.arg_num(&args, 6, 1.0)? as f32;
5574 let radius = self.arg_num(&args, 7, 0.0)? as f32;
5575 self.gfx
5576 .borrow_mut()
5577 .lights
5578 .push(Light { x, y, z, r, g, b, intensity, radius });
5579 return Ok(Value::Unit);
5580 },
5581
5582 "clear_lights" | "ล้างแสง" | "清灯" | "ライト消去" | "조명초기화" | "پاککردن_نورها" | "امسح_الأضواء" | "נקה_אורות" | "روشنیاں_صاف_کرو" | "effacer_lumières" | "lichter_löschen" | "очистить_свет" =>
5584 {
5585 self.gfx.borrow_mut().lights.clear();
5586 return Ok(Value::Unit);
5587 },
5588
5589 "set_material" | "ตั้งวัสดุ" | "设置材质" | "マテリアル設定" | "재질설정" | "تنظیم_متریال" | "عيّن_المادة" | "קבע_חומר" | "میٹریل_مقرر_کرو" =>
5599 {
5600 let key = self.arg_str(&args, 0, "");
5601 let val = self.arg_num(&args, 1, 0.0)?;
5602 let mut gfx = self.gfx.borrow_mut();
5603 let mat = gfx
5604 .material
5605 .get_or_insert_with(crate::gfx::LingMaterial::default);
5606 match key.as_str() {
5607 "albedo" => mat.albedo = val as u32,
5608 "roughness" => mat.roughness = val as f32,
5609 "metallic" => mat.metallic = val as f32,
5610 "emission" => mat.emission = val as u32,
5611 "emission_strength" => mat.emission_strength = val as f32,
5612 "specular" => mat.specular = val as f32,
5613 "specular_tint" => mat.specular_tint = val as f32,
5614 "subsurface" => mat.subsurface = val as f32,
5615 "subsurface_color" => mat.subsurface_color = val as u32,
5616 "clearcoat" => mat.clearcoat = val as f32,
5617 "clearcoat_roughness" => mat.clearcoat_roughness = val as f32,
5618 "transmission" => mat.transmission = val as f32,
5619 "ior" => mat.ior = val as f32,
5620 "iridescence" => mat.iridescence = val as f32,
5621 "sheen" => mat.sheen = val as f32,
5622 "anisotropy" => mat.anisotropy = val as f32,
5623 "anisotropy_angle" => mat.anisotropy_angle = val as f32,
5624 "toon_bands" => mat.toon_bands = val as u32,
5625 "shadow_softness" => mat.shadow_softness = val as f32,
5626 "outline_px" => mat.outline_px = val as f32,
5627 "outline_color" => mat.outline_color = val as u32,
5628 "highlight_color" => mat.highlight_color = val as u32,
5629 _ => {},
5630 }
5631 return Ok(Value::Unit);
5632 },
5633
5634 "reset_material" | "รีเซ็ตวัสดุ" | "重置材质" | "マテリアルリセット" | "재질초기화" | "بازنشانی_متریال" | "أعد_ضبط_المادة" | "אפס_חומר" | "میٹریل_ری_سیٹ" =>
5637 {
5638 self.gfx.borrow_mut().material = None;
5639 return Ok(Value::Unit);
5640 },
5641
5642 "toon_outlines"
5648 | "ตั้งเส้นขอบการ์ตูน"
5649 | "卡通轮廓"
5650 | "トゥーンアウトライン"
5651 | "툰아웃라인" | "خطوط_کارتونی" | "حدود_كرتونية" | "קווי_מתאר_מצוירים" | "ٹون_آؤٹ_لائنز" => {
5652 let px = self.arg_num(&args, 0, 0.0)? as f32;
5653 let color = self.arg_num(&args, 1, 0.0)? as u32;
5654 let thresh = self.arg_num(&args, 2, 0.05)? as f32;
5655 let mut gfx = self.gfx.borrow_mut();
5656 gfx.toon.outline_px = px;
5657 gfx.toon.outline_color = color;
5658 gfx.toon.outline_thresh = thresh;
5659 return Ok(Value::Unit);
5660 },
5661
5662 "tone_stop" | "ตั้งจุดโทน" | "色调停止" | "トーンストップ" | "톤스톱" | "نقطه_توقف_تن_رنگ" | "نقطة_توقف_اللون" | "נקודת_עצירת_גוון" | "ٹون_اسٹاپ" =>
5668 {
5669 let t = self.arg_num(&args, 0, 0.0)? as f32;
5670 let val = self.arg_num(&args, 1, 1.0)? as f32;
5671 let mut gfx = self.gfx.borrow_mut();
5672 gfx.toon.ramp.stops.push(crate::gfx::toon::ToneStop {
5673 t: t.clamp(0.0, 1.0),
5674 value: val.clamp(0.0, 1.0),
5675 });
5676 gfx.toon
5677 .ramp
5678 .stops
5679 .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
5680 return Ok(Value::Unit);
5681 },
5682
5683 "tone_smooth" | "ตั้งโทนนุ่ม" | "色调平滑" | "トーンスムーズ" | "톤스무스" | "تن_رنگ_نرم" | "تدرج_لون_ناعم" | "גוון_חלק" | "ٹون_ہموار" =>
5686 {
5687 let v = self.arg_num(&args, 0, 0.0)? as f32;
5688 self.gfx.borrow_mut().toon.ramp.smooth = v > 0.5;
5689 return Ok(Value::Unit);
5690 },
5691
5692 "tone_bezier" | "ตั้งโทนเบซิเยร์" | "色调贝塞尔" | "トーンベジェ" | "톤베지어" | "تن_رنگ_بزیه" | "تدرج_لون_بيزيه" | "גוון_בזייה" | "بیزیئر_ٹون" =>
5700 {
5701 let y1 = self.arg_num(&args, 0, 1.0 / 3.0)? as f32;
5702 let y2 = self.arg_num(&args, 1, 2.0 / 3.0)? as f32;
5703 self.gfx.borrow_mut().toon.ramp.bezier = Some([y1, y2]);
5704 return Ok(Value::Unit);
5705 },
5706
5707 "tone_bezier_off"
5709 | "ปิดโทนเบซิเยร์"
5710 | "关闭色调贝塞尔"
5711 | "トーンベジェオフ"
5712 | "톤베지어끄기" | "خاموشکردن_بزیه" | "إيقاف_تدرج_بيزيه" | "כבה_גוון_בזייה" | "بیزیئر_ٹون_بند" => {
5713 self.gfx.borrow_mut().toon.ramp.bezier = None;
5714 return Ok(Value::Unit);
5715 },
5716
5717 "tone_ramp_reset"
5719 | "รีเซ็ตการไล่โทน"
5720 | "重置色调渐变"
5721 | "トーンランプリセット"
5722 | "톤램프리셋" | "بازنشانی_شیب_تن_رنگ" | "أعد_ضبط_تدرج_اللون" | "אפס_שיפוע_גוון" | "ٹون_ریمپ_ری_سیٹ" => {
5723 self.gfx.borrow_mut().toon.ramp = crate::gfx::toon::ToneRamp::default();
5724 return Ok(Value::Unit);
5725 },
5726
5727 "tone_ramp_clear"
5729 | "ล้างการไล่โทน"
5730 | "清除色调渐变"
5731 | "トーンランプクリア"
5732 | "톤램프클리어" | "پاککردن_شیب_تن_رنگ" | "امسح_تدرج_اللون" | "נקה_שיפוע_גוון" | "ٹون_ریمپ_صاف" => {
5733 self.gfx.borrow_mut().toon.ramp.stops.clear();
5734 return Ok(Value::Unit);
5735 },
5736
5737 "tone_soft" | "โทนขอบนุ่ม" | "色调柔边" | "トーンソフト" | "톤소프트" | "تن_رنگ_لبهنرم" | "تدرج_ناعم_الحواف" | "גוון_קצה_רך" | "نرم_کنارہ_ٹون" | "tonalité_douce" | "weicher_ton" | "мягкий_тон" => {
5745 let s = self.arg_num(&args, 0, 0.32)? as f32;
5746 let sh = self.arg_num(&args, 1, 0.65)? as f32;
5747 let mut gfx = self.gfx.borrow_mut();
5748 gfx.toon.ramp.soft = s.clamp(0.0, 1.0);
5749 gfx.toon.ramp.sheen = sh.clamp(0.0, 1.0);
5750 return Ok(Value::Unit);
5751 },
5752
5753 "set_ssao" | "ตั้งเงาสัมผัส" | "环境光遮蔽" | "アンビエントオクルージョン"
5758 | "앰비언트오클루전" | "تنظیم_انسداد_محیطی" | "عيّن_تظليل_محيطي" | "קבע_הצללה_סביבתית" | "ایس_ایس_اے_او_مقرر_کرو" | "définir_ssao" | "ssao_setzen" | "задать_ssao" => {
5759 let s = self.arg_num(&args, 0, 0.35)? as f32;
5760 let r = self.arg_num(&args, 1, 6.0)? as f32;
5761 let z = self.arg_num(&args, 2, 12.0)? as f32;
5762 let mut gfx = self.gfx.borrow_mut();
5763 gfx.toon.ao_strength = s.clamp(0.0, 1.0);
5764 gfx.toon.ao_radius = r.max(1.0);
5765 gfx.toon.ao_range = z.max(0.01);
5766 return Ok(Value::Unit);
5767 },
5768
5769 "set_fxaa" | "ลบรอยหยัก" | "屏幕抗锯齿" | "画面アンチエイリアス" | "화면안티앨리어싱" | "تنظیم_ضدلبهدندانه_سریع" | "عيّن_مضاد_التسنن_السريع" | "קבע_החלקת_מסך" | "ایف_ایکس_اے_اے_مقرر_کرو" | "définir_fxaa" | "fxaa_setzen" | "задать_fxaa" => {
5775 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
5776 self.gfx.borrow_mut().toon.fxaa = on;
5777 return Ok(Value::Unit);
5778 },
5779
5780 "set_bloom" | "ตั้งบลูม" | "泛光" | "ブルーム" | "블룸" | "تنظیم_درخشش" | "عيّن_التوهج" | "קבע_זוהר" | "بلوم_مقرر_کرو" | "définir_bloom" | "bloom_setzen" | "задать_блум" => {
5785 let s = self.arg_num(&args, 0, 0.45)? as f32;
5786 let t = self.arg_num(&args, 1, 0.74)? as f32;
5787 let mut gfx = self.gfx.borrow_mut();
5788 gfx.toon.bloom_strength = s.max(0.0);
5789 gfx.toon.bloom_thresh = t.clamp(0.0, 0.99);
5790 return Ok(Value::Unit);
5791 },
5792
5793 "shadow_smooth" | "ตั้งเงานุ่ม" | "柔化阴影" | "影ソフト" | "그림자부드럽게" | "سایه_نرم" | "ظل_ناعم" | "צל_חלק" | "نرم_سایہ" =>
5796 {
5797 let s = self.arg_num(&args, 0, 0.0)? as f32;
5798 let mut gfx = self.gfx.borrow_mut();
5799 gfx.toon.ramp.smooth = s > 0.05;
5800 if s > 0.05 {
5801 let y1 = (0.333 + s * 0.2).clamp(0.0, 1.0);
5802 let y2 = (0.667 - s * 0.2).clamp(0.0, 1.0);
5803 gfx.toon.ramp.bezier = Some([y1, y2]);
5804 } else {
5805 gfx.toon.ramp.bezier = None;
5806 }
5807 return Ok(Value::Unit);
5808 },
5809
5810 "toon_highlight"
5812 | "ตั้งไฮไลท์การ์ตูน"
5813 | "卡通高光"
5814 | "トゥーンハイライト"
5815 | "툰하이라이트" | "هایلایت_کارتونی" | "إبراز_كرتوني" | "הדגשה_מצוירת" | "ٹون_ہائی_لائٹ" => {
5816 let _strength = self.arg_num(&args, 0, 0.0)? as f32;
5818 let _thresh = self.arg_num(&args, 2, 0.78)? as f32;
5819 return Ok(Value::Unit);
5821 },
5822
5823 "set_ambient" | "ตั้งแสงรอบข้าง" | "环境光" | "環境光設定" | "환경광설정" | "تنظیم_نور_محیطی" | "عيّن_الإضاءة_المحيطة" | "קבע_תאורה_סביבתית" | "ماحولیاتی_روشنی_مقرر_کرو" | "définir_ambiante" | "umgebungslicht_setzen" | "задать_фон" =>
5825 {
5826 let v = self.arg_num(&args, 0, 0.15)? as f32;
5827 self.gfx.borrow_mut().ambient = v;
5828 return Ok(Value::Unit);
5829 },
5830
5831 "set_fog" | "ตั้งหมอก" | "雾" | "霧設定" | "안개설정" | "تنظیم_مه" | "عيّن_الضباب" | "קבע_ערפל" | "دھند_مقرر_کرو" => {
5834 let r = self.arg_num(&args, 0, 0.0)?.clamp(0.0, 255.0) as u32;
5835 let g = self.arg_num(&args, 1, 0.0)?.clamp(0.0, 255.0) as u32;
5836 let b = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 255.0) as u32;
5837 let start = self.arg_num(&args, 3, 0.0)? as f32;
5838 let end = self.arg_num(&args, 4, 0.0)? as f32;
5839 let mut gfx = self.gfx.borrow_mut();
5840 gfx.fog_color = (r << 16) | (g << 8) | b;
5841 gfx.fog_start = start;
5842 gfx.fog_end = end;
5843 return Ok(Value::Unit);
5844 },
5845
5846 "วาดสามเหลี่ยม3มิติ" | "draw_triangle_3d" | "triangle3d" | "رسم_مثلث_سهبعدی" | "ارسم_مثلثا_ثلاثي_الأبعاد" | "צייר_משולש_תלת_ממדי" | "تھری_ڈی_مثلث_کھینچو" =>
5850 {
5851 let ax = self.arg_num(&args, 0, 0.0)? as f32;
5852 let ay = self.arg_num(&args, 1, 0.0)? as f32;
5853 let az = self.arg_num(&args, 2, 0.0)? as f32;
5854 let bx = self.arg_num(&args, 3, 0.0)? as f32;
5855 let by = self.arg_num(&args, 4, 0.0)? as f32;
5856 let bz = self.arg_num(&args, 5, 0.0)? as f32;
5857 let cx = self.arg_num(&args, 6, 0.0)? as f32;
5858 let cy = self.arg_num(&args, 7, 0.0)? as f32;
5859 let cz = self.arg_num(&args, 8, 0.0)? as f32;
5860
5861 let mut gfx = self.gfx.borrow_mut();
5862
5863 if gfx.mesh_capture.is_some() {
5865 let col = gfx.color;
5866 gfx.mesh_capture
5867 .as_mut()
5868 .unwrap()
5869 .push(([ax, ay, az, bx, by, bz, cx, cy, cz], col));
5870 return Ok(Value::Unit);
5871 }
5872
5873 gfx.submit_triangle(ax, ay, az, bx, by, bz, cx, cy, cz);
5874 return Ok(Value::Unit);
5875 },
5876
5877 "เริ่มอบเมช" | "mesh_bake_begin" | "شروع_پخت_مش" | "ابدأ_خبز_الشبكة" | "התחל_אפיית_רשת" | "میش_بیک_شروع" => {
5879 self.gfx.borrow_mut().mesh_capture = Some(Vec::new());
5880 return Ok(Value::Unit);
5881 },
5882
5883 "เมชแคชรับ" | "mesh_cache_get" | "دریافت_کش_مش" | "اجلب_مخبأ_الشبكة" | "קבל_מטמון_רשת" | "میش_کیش_حاصل_کرو" => {
5885 let key = self.arg_num(&args, 0, 0.0)? as i64;
5886 let h = self.gfx.borrow().mesh_cache.get(&key).copied();
5887 return Ok(Value::Number(h.map(|x| x as f64).unwrap_or(-1.0)));
5888 },
5889
5890 "เมชแคชตั้ง" | "mesh_cache_put" | "ذخیره_در_کش_مش" | "ضع_في_مخبأ_الشبكة" | "שמור_במטמון_רשת" | "میش_کیش_رکھو" => {
5892 let key = self.arg_num(&args, 0, 0.0)? as i64;
5893 let h = self.arg_num(&args, 1, 0.0)? as usize;
5894 let mut gfx = self.gfx.borrow_mut();
5895 const CAP: usize = 256;
5896 if gfx.mesh_cache.len() >= CAP {
5897 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5898 gfx.mesh_cache.clear();
5899 for id in evict {
5900 if id < gfx.meshes.len() {
5901 gfx.meshes[id].clear();
5902 gfx.mesh_free.push(id);
5903 }
5904 }
5905 }
5906 gfx.mesh_cache.insert(key, h);
5907 return Ok(Value::Unit);
5908 },
5909
5910 "เมชแคชล้าง" | "mesh_cache_clear" | "پاککردن_کش_مش" | "امسح_مخبأ_الشبكة" | "נקה_מטמון_רשת" | "میش_کیش_صاف" => {
5912 let mut gfx = self.gfx.borrow_mut();
5913 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5914 gfx.mesh_cache.clear();
5915 for id in evict {
5916 if id < gfx.meshes.len() {
5917 gfx.meshes[id].clear();
5918 gfx.mesh_free.push(id);
5919 }
5920 }
5921 return Ok(Value::Unit);
5922 },
5923
5924 "จบอบเมช" | "mesh_bake_end" | "پایان_پخت_مش" | "أنهِ_خبز_الشبكة" | "סיים_אפיית_רשת" | "میش_بیک_ختم" => {
5926 let mut gfx = self.gfx.borrow_mut();
5927 let tris = gfx.mesh_capture.take().unwrap_or_default();
5928 let id = gfx.mesh_register(tris);
5929 return Ok(Value::Number(id as f64));
5930 },
5931
5932 "วาดอบเมช" | "mesh_bake_draw" | "วาดอบเมชสี" | "mesh_bake_draw_col" | "رسم_مش_پخته" | "ارسم_شبكة_مخبوزة" | "צייר_רשת_אפויה" | "بیکڈ_میش_کھینچو" =>
5936 {
5937 let baked_col = matches!(name, "วาดอบเมชสี" | "mesh_bake_draw_col");
5938 let id = self.arg_num(&args, 0, 0.0)? as usize;
5939 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5940 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5941 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5942 let rx = self.arg_num(&args, 4, 1.0)? as f32;
5943 let ry = self.arg_num(&args, 5, 0.0)? as f32;
5944 let rz = self.arg_num(&args, 6, 0.0)? as f32;
5945 let ux = self.arg_num(&args, 7, 0.0)? as f32;
5946 let uy = self.arg_num(&args, 8, 1.0)? as f32;
5947 let uz = self.arg_num(&args, 9, 0.0)? as f32;
5948 let s = self.arg_num(&args, 10, 1.0)? as f32;
5949 self.gfx
5950 .borrow_mut()
5951 .mesh_draw(id, ox, oy, oz, rx, ry, rz, ux, uy, uz, s, baked_col);
5952 return Ok(Value::Unit);
5953 },
5954
5955 "draw_quad_3d"
5959 | "quad3d"
5960 | "วาดสี่เหลี่ยม3มิติ"
5961 | "draw_pent_3d"
5962 | "pent3d"
5963 | "วาดห้าเหลี่ยม3มิติ"
5964 | "draw_hex_3d"
5965 | "hex3d"
5966 | "วาดหกเหลี่ยม3มิติ"
5967 | "draw_polygon_3d"
5968 | "polygon3d"
5969 | "วาดรูปหลายเหลี่ยม3มิติ" | "رسم_چهارضلعی_سهبعدی" | "ارسم_رباعيا_ثلاثي_الأبعاد" | "צייר_מרובע_תלת_ממדי" | "تھری_ڈی_چوکور_کھینچو" => {
5970 let mut wxs: [f32; 8] = [0.0; 8];
5972 let mut wys: [f32; 8] = [0.0; 8];
5973 let mut wzs: [f32; 8] = [0.0; 8];
5974 let n_verts;
5975
5976 if args.len() == 1 {
5977 let list = match &args[0] {
5979 Value::List(l) => l.clone(),
5980 _ => {
5981 return Err(EvalErr::from("draw_polygon_3d: expected list".to_string()))
5982 },
5983 };
5984 let coords: Vec<f32> = list
5985 .iter()
5986 .map(|v| match v {
5987 Value::Number(n) => *n as f32,
5988 _ => 0.0,
5989 })
5990 .collect();
5991 n_verts = (coords.len() / 3).min(8);
5992 for i in 0..n_verts {
5993 wxs[i] = coords[i * 3];
5994 wys[i] = coords[i * 3 + 1];
5995 wzs[i] = coords[i * 3 + 2];
5996 }
5997 } else {
5998 n_verts = (args.len() / 3).min(8);
6000 for i in 0..n_verts {
6001 wxs[i] = self.arg_num(&args, i * 3, 0.0)? as f32;
6002 wys[i] = self.arg_num(&args, i * 3 + 1, 0.0)? as f32;
6003 wzs[i] = self.arg_num(&args, i * 3 + 2, 0.0)? as f32;
6004 }
6005 }
6006 if n_verts < 3 {
6007 return Ok(Value::Unit);
6008 }
6009
6010 let mut gfx = self.gfx.borrow_mut();
6011
6012 if gfx.mesh_capture.is_some() {
6018 let col = gfx.color;
6019 let cap = gfx.mesh_capture.as_mut().unwrap();
6020 for i in 1..n_verts - 1 {
6021 cap.push((
6022 [
6023 wxs[0], wys[0], wzs[0],
6024 wxs[i], wys[i], wzs[i],
6025 wxs[i + 1], wys[i + 1], wzs[i + 1],
6026 ],
6027 col,
6028 ));
6029 }
6030 return Ok(Value::Unit);
6031 }
6032
6033 let normal = crate::gfx::poly::face_normal(
6035 wxs[0], wys[0], wzs[0], wxs[1], wys[1], wzs[1], wxs[2], wys[2], wzs[2],
6036 );
6037
6038 let mut wcs: [u32; 8] = [0; 8];
6040 if gfx.flat_shade {
6041 let c = gfx.color;
6042 for wc in wcs.iter_mut().take(n_verts) {
6043 *wc = c;
6044 }
6045 } else if let Some(ref mat) = gfx.material.clone() {
6046 let cam = [gfx.camera.cx, gfx.camera.cy, gfx.camera.zdist];
6047 let lights: Vec<_> = gfx.lights.clone();
6048 let ambient = gfx.ambient;
6049 for i in 0..n_verts {
6050 let v = [wxs[i], wys[i], wzs[i]];
6051 let vd = [cam[0] - v[0], cam[1] - v[1], cam[2] - v[2]];
6052 wcs[i] = crate::gfx::material::shade(mat, normal, vd, v, &lights, ambient);
6053 }
6054 } else {
6055 let base = gfx.color;
6056 let lights: Vec<_> = gfx.lights.clone();
6057 let ambient = gfx.ambient;
6058 for i in 0..n_verts {
6059 wcs[i] = crate::gfx::light::compute_lit_color_linear(
6060 base,
6061 normal,
6062 [wxs[i], wys[i], wzs[i]],
6063 &lights,
6064 ambient,
6065 );
6066 }
6067 }
6068
6069 let near = -gfx.camera.zdist + 0.05;
6071 let mut clip_in: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6072 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6073 for i in 0..n_verts {
6074 let d = gfx.camera.depth(wxs[i], wys[i], wzs[i]);
6075 clip_in[i] = (wxs[i], wys[i], wzs[i], d, wcs[i]);
6076 }
6077 let mut clip_out: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6078 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6079 let pn = crate::gfx::poly::clip_near(&clip_in, n_verts, near, &mut clip_out);
6080 if pn < 3 {
6081 return Ok(Value::Unit);
6082 }
6083
6084 let mut proj: [(f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6086 [(0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6087 for i in 0..pn {
6088 let (sx, sy, sz) =
6089 gfx.camera
6090 .project(clip_out[i].0, clip_out[i].1, clip_out[i].2);
6091 let fc = gfx.fog_apply(clip_out[i].4, sz);
6092 proj[i] = (sx, sy, sz, fc);
6093 }
6094
6095 let unlit = gfx.flat_shade;
6097 crate::gfx::poly::fan_emit_proj(
6098 &proj,
6099 pn,
6100 |x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2| {
6101 gfx.depth_queue.push_triangle_g_zv(
6102 x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2, 3, unlit,
6103 );
6104 },
6105 );
6106 return Ok(Value::Unit);
6107 },
6108
6109 "วาดเส้น3มิติ" | "draw_line_3d" | "line3d" | "画3D线" | "3D線描く" | "3D선그리기" | "رسم_خط_سهبعدی" | "ارسم_خطا_ثلاثي_الأبعاد" | "צייר_קו_תלת_ממדי" | "تھری_ڈی_لکیر_کھینچو" =>
6113 {
6114 let ax = self.arg_num(&args, 0, 0.0)? as f32;
6115 let ay = self.arg_num(&args, 1, 0.0)? as f32;
6116 let az = self.arg_num(&args, 2, 0.0)? as f32;
6117 let bx = self.arg_num(&args, 3, 0.0)? as f32;
6118 let by = self.arg_num(&args, 4, 0.0)? as f32;
6119 let bz = self.arg_num(&args, 5, 0.0)? as f32;
6120
6121 let mut gfx = self.gfx.borrow_mut();
6122 let color = gfx.color;
6123 let near = -gfx.camera.zdist + 0.05;
6125 let mut lax = ax;
6126 let mut lay = ay;
6127 let mut laz = az;
6128 let mut lbx = bx;
6129 let mut lby = by;
6130 let mut lbz = bz;
6131 let da_raw = gfx.camera.depth(lax, lay, laz);
6132 let db_raw = gfx.camera.depth(lbx, lby, lbz);
6133 if da_raw <= near && db_raw <= near {
6134 return Ok(Value::Unit);
6135 }
6136 if da_raw <= near {
6137 let t = (near - da_raw) / (db_raw - da_raw);
6138 lax += t * (lbx - lax);
6139 lay += t * (lby - lay);
6140 laz += t * (lbz - laz);
6141 } else if db_raw <= near {
6142 let t = (near - da_raw) / (db_raw - da_raw);
6143 lbx = lax + t * (lbx - lax);
6144 lby = lay + t * (lby - lay);
6145 lbz = laz + t * (lbz - laz);
6146 }
6147 if !gfx.edge_set.try_insert(lax, lay, laz, lbx, lby, lbz) {
6149 return Ok(Value::Unit);
6150 }
6151 let (sax, say, da) = gfx.camera.project(lax, lay, laz);
6152 let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
6153 let depth = (da + db) / 2.0;
6154 let color = gfx.fog_apply(color, depth);
6155 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
6156 return Ok(Value::Unit);
6157 },
6158
6159 #[cfg(not(target_arch = "wasm32"))]
6169 "orb_shell" | "球壳" | "オーブ殻" | "오브껍질" | "เปลือกทรงกลม" | "پوسته_کروی" | "قشرة_كروية" | "קליפת_כדור" | "کروی_خول" | "coque_orbe" | "orb_hülle" | "оболочка_сферы" =>
6170 {
6171 let cx = self.arg_num(&args, 0, 0.)? as f32;
6172 let cy = self.arg_num(&args, 1, 0.)? as f32;
6173 let cz = self.arg_num(&args, 2, 0.)? as f32;
6174 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6175 let ry = self.arg_num(&args, 4, 0.)? as f32;
6176 let rx = self.arg_num(&args, 5, 0.)? as f32;
6177 let density = (self.arg_num(&args, 6, 10.)? as i32).clamp(1, 48);
6178 let tr = (self.arg_num(&args, 7, 230.)? as f32).clamp(0., 255.);
6179 let tg = (self.arg_num(&args, 8, 230.)? as f32).clamp(0., 255.);
6180 let tb = (self.arg_num(&args, 9, 235.)? as f32).clamp(0., 255.);
6181 let (cyr, syr) = (ry.cos(), ry.sin());
6182 let (cxr, sxr) = (rx.cos(), rx.sin());
6183 let tau = std::f32::consts::TAU;
6184 let pi = std::f32::consts::PI;
6185 let turns = 6.0_f32; let nseg = 96; let inv_r = if radius.abs() > 1e-5 {
6188 1.0 / radius
6189 } else {
6190 0.0
6191 };
6192 let pt = |u: f32, theta0: f32, dir: f32| -> ([f32; 3], f32) {
6195 let phi = pi * u; let th = dir * turns * tau * u + theta0;
6197 let (mut x, y, mut z) = (
6198 phi.sin() * th.cos() * radius,
6199 phi.cos() * radius,
6200 phi.sin() * th.sin() * radius,
6201 );
6202 let x1 = x * cyr + z * syr; let z1 = -x * syr + z * cyr;
6204 x = x1;
6205 z = z1;
6206 let y2 = y * cxr - z * sxr; let z2 = y * sxr + z * cxr;
6208 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.0, 1.0);
6210 ([cx + x, cy + y2, cz + z2], facing)
6211 };
6212 let mut gfx = self.gfx.borrow_mut();
6213 let near = -gfx.camera.zdist + 0.05;
6214 let seg = |gfx: &mut crate::gfx::GfxState, a: [f32; 3], b: [f32; 3], lum: f32| {
6216 let (mut lax, mut lay, mut laz) = (a[0], a[1], a[2]);
6217 let (mut lbx, mut lby, mut lbz) = (b[0], b[1], b[2]);
6218 let da = gfx.camera.depth(lax, lay, laz);
6219 let db = gfx.camera.depth(lbx, lby, lbz);
6220 if da <= near && db <= near {
6221 return;
6222 }
6223 if da <= near {
6224 let t = (near - da) / (db - da);
6225 lax += t * (lbx - lax);
6226 lay += t * (lby - lay);
6227 laz += t * (lbz - laz);
6228 } else if db <= near {
6229 let t = (near - da) / (db - da);
6230 lbx = lax + t * (lbx - lax);
6231 lby = lay + t * (lby - lay);
6232 lbz = laz + t * (lbz - laz);
6233 }
6234 let (sax, say, da2) = gfx.camera.project(lax, lay, laz);
6235 let (sbx, sby, db2) = gfx.camera.project(lbx, lby, lbz);
6236 let l = (0.12 + 0.88 * lum).clamp(0.0, 1.0);
6238 let cr = (tr * l) as u32;
6239 let cg = (tg * l) as u32;
6240 let cb = (tb * l) as u32;
6241 let color = (cr << 16) | (cg << 8) | cb;
6242 gfx.depth_queue
6243 .push_line((da2 + db2) * 0.5, color, sax, say, sbx, sby);
6244 };
6245 for &dir in &[1.0_f32, -1.0_f32] {
6247 for s in 0..density {
6248 let theta0 = s as f32 * tau / density as f32;
6249 let mut prev = pt(0.0, theta0, dir);
6250 for k in 1..=nseg {
6251 let cur = pt(k as f32 / nseg as f32, theta0, dir);
6252 seg(&mut gfx, prev.0, cur.0, (prev.1 + cur.1) * 0.5);
6253 prev = cur;
6254 }
6255 }
6256 }
6257 return Ok(Value::Unit);
6258 },
6259
6260 #[cfg(not(target_arch = "wasm32"))]
6268 "orb_particles" | "球内粒子" | "オーブ粒子" | "오브입자" | "อนุภาคทรงกลม" | "ذرات_کروی" | "جسيمات_كروية" | "חלקיקי_כדור" | "کروی_ذرات" | "particules_orbe" | "orb_partikel" | "частицы_сферы" =>
6269 {
6270 let cx = self.arg_num(&args, 0, 0.)? as f32;
6271 let cy = self.arg_num(&args, 1, 0.)? as f32;
6272 let cz = self.arg_num(&args, 2, 0.)? as f32;
6273 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6274 let count = (self.arg_num(&args, 4, 160.)? as i32).clamp(1, 4000);
6275 let t = self.arg_num(&args, 5, 0.)? as f32;
6276 let tr = (self.arg_num(&args, 6, 255.)? as f32).clamp(0., 255.);
6277 let tg = (self.arg_num(&args, 7, 255.)? as f32).clamp(0., 255.);
6278 let tb = (self.arg_num(&args, 8, 255.)? as f32).clamp(0., 255.);
6279 let inv_r = if radius.abs() > 1e-5 {
6280 1.0 / radius
6281 } else {
6282 0.0
6283 };
6284 let h = |mut x: u32| -> f32 {
6286 x = x.wrapping_mul(747796405).wrapping_add(2891336453);
6287 x = ((x >> ((x >> 28).wrapping_add(4))) ^ x).wrapping_mul(277803737);
6288 (((x >> 22) ^ x) & 0xFFFFFF) as f32 / 16_777_216.0
6289 };
6290 let tau = std::f32::consts::TAU;
6291 let (cyr, syr) = ((t * 0.5).cos(), (t * 0.5).sin());
6293 let (cxr, sxr) = ((t * 0.23).cos(), (t * 0.23).sin());
6294 let mut gfx = self.gfx.borrow_mut();
6295 let near = -gfx.camera.zdist + 0.05;
6296 let (sw, sh) = (gfx.width as i32, gfx.height as i32);
6297 for i in 0..count {
6298 let i = i as u32;
6299 let u = h(i.wrapping_mul(3) + 1);
6301 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) = (
6305 rr * ph.sin() * th.cos(),
6306 rr * ph.cos(),
6307 rr * ph.sin() * th.sin(),
6308 );
6309 let x1 = x * cyr + z * syr;
6311 let z1 = -x * syr + z * cyr;
6312 x = x1;
6313 z = z1;
6314 let y2 = y * cxr - z * sxr;
6315 let z2 = y * sxr + z * cxr;
6316 let (wx, wy, wz) = (cx + x, cy + y2, cz + z2);
6317 if gfx.camera.depth(wx, wy, wz) <= near {
6318 continue;
6319 }
6320 let (sx, sy, dep) = gfx.camera.project(wx, wy, wz);
6321 let sxi = sx as i32;
6322 let syi = sy as i32;
6323 if sxi < 0 || syi < 0 || sxi >= sw || syi >= sh {
6324 continue;
6325 }
6326 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.15, 1.0);
6328 let l = facing;
6329 let cr = (tr * l) as u32;
6330 let cg = (tg * l) as u32;
6331 let cb = (tb * l) as u32;
6332 let color = (cr << 16) | (cg << 8) | cb;
6333 let len = if facing > 0.7 { 1.0 } else { 0.0 };
6335 gfx.depth_queue.push_line(dep, color, sx, sy, sx + len, sy);
6336 }
6337 return Ok(Value::Unit);
6338 },
6339
6340 "project_3d" | "投影3D" | "3D投影" | "3D투영" | "ฉาย3มิติ" | "فرافکنی_سهبعدی" | "إسقاط_ثلاثي_الأبعاد" | "הטלה_תלת_ממדית" | "تھری_ڈی_پروجیکشن" | "projeter_3d" | "projizieren_3d" | "проекция_3d" => {
6344 let x = self.arg_num(&args, 0, 0.0)? as f32;
6345 let y = self.arg_num(&args, 1, 0.0)? as f32;
6346 let z = self.arg_num(&args, 2, 0.0)? as f32;
6347 let gfx = self.gfx.borrow();
6348 let near = -gfx.camera.zdist + 0.05;
6349 let d = gfx.camera.depth(x, y, z);
6350 if d <= near {
6351 return Ok(Value::List(Rc::new(vec![
6352 Value::Number(-99999.0),
6353 Value::Number(-99999.0),
6354 Value::Number(d as f64),
6355 ])));
6356 }
6357 let (sx, sy, depth) = gfx.camera.project(x, y, z);
6358 return Ok(Value::List(Rc::new(vec![
6359 Value::Number(sx as f64),
6360 Value::Number(sy as f64),
6361 Value::Number(depth as f64),
6362 ])));
6363 },
6364
6365 #[cfg(not(target_arch = "wasm32"))]
6372 "mouse_ray" => {
6373 let gfx = self.gfx.borrow();
6374 let (mx, my) = gfx
6375 .window
6376 .as_ref()
6377 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
6378 .unwrap_or((gfx.camera.cx, gfx.camera.cy));
6379 let cam = &gfx.camera;
6380 let dcx = (mx - cam.cx) / cam.focal;
6383 let dcy = (my - cam.cy) / cam.focal;
6384 let a_x = dcx;
6387 let a_y = cam.crx * dcy + cam.srx * 1.0;
6388 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6389 let dir_x = cam.cry * a_x + cam.sry * a_z;
6390 let dir_y = a_y;
6391 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6392 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6393 .sqrt()
6394 .max(1e-6);
6395 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6396 let ox = cam.tx;
6408 let oy = cam.ty;
6409 let oz = cam.tz;
6410 return Ok(Value::List(Rc::new(vec![
6411 Value::Number(ox as f64),
6412 Value::Number(oy as f64),
6413 Value::Number(oz as f64),
6414 Value::Number(dir_x as f64),
6415 Value::Number(dir_y as f64),
6416 Value::Number(dir_z as f64),
6417 ])));
6418 },
6419 #[cfg(target_arch = "wasm32")]
6420 "mouse_ray" => {
6421 let gfx = self.gfx.borrow();
6422 let mx = crate::gfx::wasm_mouse_x();
6423 let my = crate::gfx::wasm_mouse_y();
6424 let cam = &gfx.camera;
6425 let dcx = (mx - cam.cx) / cam.focal;
6426 let dcy = (my - cam.cy) / cam.focal;
6427 let a_x = dcx;
6428 let a_y = cam.crx * dcy + cam.srx * 1.0;
6429 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6430 let dir_x = cam.cry * a_x + cam.sry * a_z;
6431 let dir_y = a_y;
6432 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6433 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6434 .sqrt()
6435 .max(1e-6);
6436 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6437 let ox = cam.tx;
6438 let oy = cam.ty;
6439 let oz = cam.tz;
6440 return Ok(Value::List(Rc::new(vec![
6441 Value::Number(ox as f64),
6442 Value::Number(oy as f64),
6443 Value::Number(oz as f64),
6444 Value::Number(dir_x as f64),
6445 Value::Number(dir_y as f64),
6446 Value::Number(dir_z as f64),
6447 ])));
6448 },
6449 #[cfg(not(target_arch = "wasm32"))]
6452 "draw_poly" | "填充多边形" | "ポリゴン塗り" | "다각형채우기" | "เติมรูปหลายเหลี่ยม" | "رسم_چندضلعی" | "ارسم_مضلع" | "צייר_מצולע" | "کثیر_الاضلاع_کھینچو" | "dessiner_polygone" | "polygon_zeichnen" | "рисовать_полигон" =>
6453 {
6454 let mut pts: Vec<[f32; 2]> = Vec::new();
6455 if let Some(Value::List(v)) = args.first() {
6456 let mut i = 0;
6457 while i + 1 < v.len() {
6458 let x = self.to_number(&v[i]).unwrap_or(0.0) as f32;
6459 let y = self.to_number(&v[i + 1]).unwrap_or(0.0) as f32;
6460 pts.push([x, y]);
6461 i += 2;
6462 }
6463 }
6464 if pts.len() >= 3 {
6465 if pts[0] != pts[pts.len() - 1] {
6466 let p0 = pts[0];
6467 pts.push(p0);
6468 } let mut gfx = self.gfx.borrow_mut();
6470 let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
6471 crate::gfx::raster::fill_contours_aa(
6472 &mut gfx.buffer,
6473 w,
6474 h,
6475 color,
6476 add,
6477 std::slice::from_ref(&pts),
6478 );
6479 }
6480 return Ok(Value::Unit);
6481 },
6482
6483 "vtex_grid" | "ลายตาราง" | "纹格" | "格子模様" | "격자무늬" | "الگوی_شبکه" | "نقش_شبكة" | "דוגמת_רשת" | "نقش_جالی" | "motif_grille" | "muster_gitter" | "узор_сетка" =>
6492 {
6493 let cx = self.arg_num(&args, 0, 0.)? as f32;
6494 let cy = self.arg_num(&args, 1, 0.)? as f32;
6495 let cz = self.arg_num(&args, 2, 0.)? as f32;
6496 let ux = self.arg_num(&args, 3, 1.)? as f32;
6497 let uy = self.arg_num(&args, 4, 0.)? as f32;
6498 let uz = self.arg_num(&args, 5, 0.)? as f32;
6499 let vx = self.arg_num(&args, 6, 0.)? as f32;
6500 let vy = self.arg_num(&args, 7, 0.)? as f32;
6501 let vz = self.arg_num(&args, 8, 1.)? as f32;
6502 let cols = self.arg_num(&args, 9, 10.)? as usize;
6503 let rows = self.arg_num(&args, 10, 10.)? as usize;
6504 let cw = self.arg_num(&args, 11, 1.)? as f32;
6505 let ch = self.arg_num(&args, 12, 1.)? as f32;
6506 let fr = self.arg_num(&args, 13, 0.)? as f32;
6507 let hue = self.arg_num(&args, 14, 0.)? as f32;
6508 let mut gfx = self.gfx.borrow_mut();
6509 let cam = gfx.camera.clone();
6510 crate::gfx::vtex::draw_grid(
6511 &mut gfx.depth_queue,
6512 &cam,
6513 cx,
6514 cy,
6515 cz,
6516 ux,
6517 uy,
6518 uz,
6519 vx,
6520 vy,
6521 vz,
6522 cols,
6523 rows,
6524 cw,
6525 ch,
6526 fr,
6527 hue,
6528 );
6529 return Ok(Value::Unit);
6530 },
6531
6532 "vtex_rings" | "ลายวงซ้อน" | "纹环" | "同心円" | "동심원" | "الگوی_حلقه" | "نقش_حلقات" | "דוגמת_טבעות" | "نقش_حلقے" | "motif_anneaux" | "muster_ringe" | "узор_кольца" => {
6534 let cx = self.arg_num(&args, 0, 0.)? as f32;
6535 let cy = self.arg_num(&args, 1, 0.)? as f32;
6536 let cz = self.arg_num(&args, 2, 0.)? as f32;
6537 let ux = self.arg_num(&args, 3, 1.)? as f32;
6538 let uy = self.arg_num(&args, 4, 0.)? as f32;
6539 let uz = self.arg_num(&args, 5, 0.)? as f32;
6540 let vx = self.arg_num(&args, 6, 0.)? as f32;
6541 let vy = self.arg_num(&args, 7, 0.)? as f32;
6542 let vz = self.arg_num(&args, 8, 1.)? as f32;
6543 let nr = self.arg_num(&args, 9, 6.)? as usize;
6544 let ns = self.arg_num(&args, 10, 6.)? as usize;
6545 let mr = self.arg_num(&args, 11, 3.)? as f32;
6546 let tw = self.arg_num(&args, 12, 0.)? as f32;
6547 let fr = self.arg_num(&args, 13, 0.)? as f32;
6548 let hue = self.arg_num(&args, 14, 0.)? as f32;
6549 let mut gfx = self.gfx.borrow_mut();
6550 let cam = gfx.camera.clone();
6551 crate::gfx::vtex::draw_rings(
6552 &mut gfx.depth_queue,
6553 &cam,
6554 cx,
6555 cy,
6556 cz,
6557 ux,
6558 uy,
6559 uz,
6560 vx,
6561 vy,
6562 vz,
6563 nr,
6564 ns,
6565 mr,
6566 tw,
6567 fr,
6568 hue,
6569 );
6570 return Ok(Value::Unit);
6571 },
6572
6573 "vtex_star" | "ลายดาว" | "纹星" | "星模様" | "별무늬" | "الگوی_ستاره" | "نقش_نجمة" | "דוגמת_כוכב" | "نقش_ستارہ" | "motif_étoile" | "muster_stern" | "узор_звезда" => {
6575 let cx = self.arg_num(&args, 0, 0.)? as f32;
6576 let cy = self.arg_num(&args, 1, 0.)? as f32;
6577 let cz = self.arg_num(&args, 2, 0.)? as f32;
6578 let ux = self.arg_num(&args, 3, 1.)? as f32;
6579 let uy = self.arg_num(&args, 4, 0.)? as f32;
6580 let uz = self.arg_num(&args, 5, 0.)? as f32;
6581 let vx = self.arg_num(&args, 6, 0.)? as f32;
6582 let vy = self.arg_num(&args, 7, 0.)? as f32;
6583 let vz = self.arg_num(&args, 8, 1.)? as f32;
6584 let np = self.arg_num(&args, 9, 6.)? as usize;
6585 let ro = self.arg_num(&args, 10, 2.)? as f32;
6586 let ri = self.arg_num(&args, 11, 1.)? as f32;
6587 let rs = self.arg_num(&args, 12, 0.01)? as f32;
6588 let fr = self.arg_num(&args, 13, 0.)? as f32;
6589 let hue = self.arg_num(&args, 14, 0.)? as f32;
6590 let mut gfx = self.gfx.borrow_mut();
6591 let cam = gfx.camera.clone();
6592 crate::gfx::vtex::draw_star(
6593 &mut gfx.depth_queue,
6594 &cam,
6595 cx,
6596 cy,
6597 cz,
6598 ux,
6599 uy,
6600 uz,
6601 vx,
6602 vy,
6603 vz,
6604 np,
6605 ro,
6606 ri,
6607 rs,
6608 fr,
6609 hue,
6610 );
6611 return Ok(Value::Unit);
6612 },
6613
6614 "vtex_spiral" | "ลายเกลียว" | "纹螺" | "螺旋" | "나선" | "الگوی_مارپیچ" | "نقش_حلزوني" | "דוגמת_ספירלה" | "نقش_سرپیچ" | "motif_spirale" | "muster_spirale" | "узор_спираль" => {
6616 let cx = self.arg_num(&args, 0, 0.)? as f32;
6617 let cy = self.arg_num(&args, 1, 0.)? as f32;
6618 let cz = self.arg_num(&args, 2, 0.)? as f32;
6619 let ux = self.arg_num(&args, 3, 1.)? as f32;
6620 let uy = self.arg_num(&args, 4, 0.)? as f32;
6621 let uz = self.arg_num(&args, 5, 0.)? as f32;
6622 let vx = self.arg_num(&args, 6, 0.)? as f32;
6623 let vy = self.arg_num(&args, 7, 0.)? as f32;
6624 let vz = self.arg_num(&args, 8, 1.)? as f32;
6625 let nt = self.arg_num(&args, 9, 3.)? as f32;
6626 let mr = self.arg_num(&args, 10, 3.)? as f32;
6627 let st = self.arg_num(&args, 11, 120.)? as usize;
6628 let fr = self.arg_num(&args, 12, 0.)? as f32;
6629 let hue = self.arg_num(&args, 13, 0.)? as f32;
6630 let mut gfx = self.gfx.borrow_mut();
6631 let cam = gfx.camera.clone();
6632 crate::gfx::vtex::draw_spiral(
6633 &mut gfx.depth_queue,
6634 &cam,
6635 cx,
6636 cy,
6637 cz,
6638 ux,
6639 uy,
6640 uz,
6641 vx,
6642 vy,
6643 vz,
6644 nt,
6645 mr,
6646 st,
6647 fr,
6648 hue,
6649 );
6650 return Ok(Value::Unit);
6651 },
6652
6653 "vtex_flower" | "ลายดอก" | "纹花" | "花模様" | "꽃무늬" | "الگوی_گل" | "نقش_زهرة" | "דוגמת_פרח" | "نقش_پھول" | "motif_fleur" | "muster_blume" | "узор_цветок" => {
6655 let cx = self.arg_num(&args, 0, 0.)? as f32;
6656 let cy = self.arg_num(&args, 1, 0.)? as f32;
6657 let cz = self.arg_num(&args, 2, 0.)? as f32;
6658 let ux = self.arg_num(&args, 3, 1.)? as f32;
6659 let uy = self.arg_num(&args, 4, 0.)? as f32;
6660 let uz = self.arg_num(&args, 5, 0.)? as f32;
6661 let vx = self.arg_num(&args, 6, 0.)? as f32;
6662 let vy = self.arg_num(&args, 7, 0.)? as f32;
6663 let vz = self.arg_num(&args, 8, 1.)? as f32;
6664 let r = self.arg_num(&args, 9, 1.)? as f32;
6665 let ns = self.arg_num(&args, 10, 24.)? as usize;
6666 let fr = self.arg_num(&args, 11, 0.)? as f32;
6667 let hue = self.arg_num(&args, 12, 0.)? as f32;
6668 let mut gfx = self.gfx.borrow_mut();
6669 let cam = gfx.camera.clone();
6670 crate::gfx::vtex::draw_flower(
6671 &mut gfx.depth_queue,
6672 &cam,
6673 cx,
6674 cy,
6675 cz,
6676 ux,
6677 uy,
6678 uz,
6679 vx,
6680 vy,
6681 vz,
6682 r,
6683 ns,
6684 fr,
6685 hue,
6686 );
6687 return Ok(Value::Unit);
6688 },
6689
6690 "vtex_letter_rain" | "ลายอักษรไหล" | "纹字雨" | "文字雨" | "글자비" | "الگوی_باران_حروف" | "نقش_مطر_الحروف" | "דוגמת_גשם_אותיות" | "نقش_حروف_بارش" | "motif_pluie_lettres" | "muster_buchstabenregen" | "узор_дождь_букв" =>
6692 {
6693 let cx = self.arg_num(&args, 0, 0.)? as f32;
6694 let cy = self.arg_num(&args, 1, 0.)? as f32;
6695 let cz = self.arg_num(&args, 2, 0.)? as f32;
6696 let ux = self.arg_num(&args, 3, 1.)? as f32;
6697 let uy = self.arg_num(&args, 4, 0.)? as f32;
6698 let uz = self.arg_num(&args, 5, 0.)? as f32;
6699 let vx = self.arg_num(&args, 6, 0.)? as f32;
6700 let vy = self.arg_num(&args, 7, 0.)? as f32;
6701 let vz = self.arg_num(&args, 8, 1.)? as f32;
6702 let nc = self.arg_num(&args, 9, 16.)? as usize;
6703 let nv = self.arg_num(&args, 10, 14.)? as usize;
6704 let cw = self.arg_num(&args, 11, 0.65)? as f32;
6705 let rh = self.arg_num(&args, 12, 0.60)? as f32;
6706 let sp = self.arg_num(&args, 13, 0.025)? as f32;
6707 let fr = self.arg_num(&args, 14, 0.)? as f32;
6708 let hue = self.arg_num(&args, 15, 0.)? as f32;
6709 let mut gfx = self.gfx.borrow_mut();
6710 let cam = gfx.camera.clone();
6711 crate::gfx::vtex::draw_letter_rain(
6712 &mut gfx.depth_queue,
6713 &cam,
6714 cx,
6715 cy,
6716 cz,
6717 ux,
6718 uy,
6719 uz,
6720 vx,
6721 vy,
6722 vz,
6723 nc,
6724 nv,
6725 cw,
6726 rh,
6727 sp,
6728 fr,
6729 hue,
6730 );
6731 return Ok(Value::Unit);
6732 },
6733
6734 "vtex_hyperbolic_uv" | "ลายไฮเพอร์โบลิก" | "纹曲面" | "双曲線" | "쌍곡선" | "الگوی_هذلولی" | "نقش_زائدي" | "דוגמת_היפרבולית" | "نقش_ہائپربولک" | "motif_uv_hyperbolique" | "muster_hyperbolische_uv" | "узор_гиперболический_uv" =>
6736 {
6737 let cx = self.arg_num(&args, 0, 0.)? as f32;
6738 let cy = self.arg_num(&args, 1, 0.)? as f32;
6739 let cz = self.arg_num(&args, 2, 0.)? as f32;
6740 let ux = self.arg_num(&args, 3, 1.)? as f32;
6741 let uy = self.arg_num(&args, 4, 0.)? as f32;
6742 let uz = self.arg_num(&args, 5, 0.)? as f32;
6743 let vx = self.arg_num(&args, 6, 0.)? as f32;
6744 let vy = self.arg_num(&args, 7, 0.)? as f32;
6745 let vz = self.arg_num(&args, 8, 1.)? as f32;
6746 let mr = self.arg_num(&args, 9, 5.)? as f32;
6747 let nc = self.arg_num(&args, 10, 12.)? as usize;
6748 let nr = self.arg_num(&args, 11, 18.)? as usize;
6749 let fr = self.arg_num(&args, 12, 0.)? as f32;
6750 let hue = self.arg_num(&args, 13, 0.)? as f32;
6751 let mut gfx = self.gfx.borrow_mut();
6752 let cam = gfx.camera.clone();
6753 crate::gfx::vtex::draw_hyperbolic_uv(
6754 &mut gfx.depth_queue,
6755 &cam,
6756 cx,
6757 cy,
6758 cz,
6759 ux,
6760 uy,
6761 uz,
6762 vx,
6763 vy,
6764 vz,
6765 mr,
6766 nc,
6767 nr,
6768 fr,
6769 hue,
6770 );
6771 return Ok(Value::Unit);
6772 },
6773
6774 "vtex_halftone" | "ลายจุด" | "纹半调" | "網点模様" | "망점" | "الگوی_نیمتن" | "نقش_نصفي" | "דוגמת_חצי_גוון" | "نقش_ہاف_ٹون" | "motif_demi_ton" | "muster_halbton" | "узор_растр" => {
6776 let cx = self.arg_num(&args, 0, 0.)? as f32;
6777 let cy = self.arg_num(&args, 1, 0.)? as f32;
6778 let cz = self.arg_num(&args, 2, 0.)? as f32;
6779 let ux = self.arg_num(&args, 3, 1.)? as f32;
6780 let uy = self.arg_num(&args, 4, 0.)? as f32;
6781 let uz = self.arg_num(&args, 5, 0.)? as f32;
6782 let vx = self.arg_num(&args, 6, 0.)? as f32;
6783 let vy = self.arg_num(&args, 7, 0.)? as f32;
6784 let vz = self.arg_num(&args, 8, 1.)? as f32;
6785 let cols = self.arg_num(&args, 9, 16.)? as usize;
6786 let rows = self.arg_num(&args, 10, 12.)? as usize;
6787 let cw = self.arg_num(&args, 11, 0.5)? as f32;
6788 let ch = self.arg_num(&args, 12, 0.5)? as f32;
6789 let dens = self.arg_num(&args, 13, 0.4)? as f32;
6790 let fr = self.arg_num(&args, 14, 0.)? as f32;
6791 let hue = self.arg_num(&args, 15, 0.)? as f32;
6792 let mut gfx = self.gfx.borrow_mut();
6793 let cam = gfx.camera.clone();
6794 crate::gfx::vtex::draw_halftone(
6795 &mut gfx.depth_queue,
6796 &cam,
6797 cx,
6798 cy,
6799 cz,
6800 ux,
6801 uy,
6802 uz,
6803 vx,
6804 vy,
6805 vz,
6806 cols,
6807 rows,
6808 cw,
6809 ch,
6810 dens,
6811 fr,
6812 hue,
6813 );
6814 return Ok(Value::Unit);
6815 },
6816
6817 "vtex_tessellated" | "ลายตาข่าย" | "纹镶嵌" | "網目模様" | "격자망" | "الگوی_کاشیکاری" | "نقش_مرصوف_متكرر" | "דוגמת_ריצוף_חוזר" | "نقش_ٹائلنگ" | "motif_tesselle" | "muster_tessellation" | "узор_мозаика" =>
6819 {
6820 let cx = self.arg_num(&args, 0, 0.)? as f32;
6821 let cy = self.arg_num(&args, 1, 0.)? as f32;
6822 let cz = self.arg_num(&args, 2, 0.)? as f32;
6823 let ux = self.arg_num(&args, 3, 1.)? as f32;
6824 let uy = self.arg_num(&args, 4, 0.)? as f32;
6825 let uz = self.arg_num(&args, 5, 0.)? as f32;
6826 let vx = self.arg_num(&args, 6, 0.)? as f32;
6827 let vy = self.arg_num(&args, 7, 0.)? as f32;
6828 let vz = self.arg_num(&args, 8, 1.)? as f32;
6829 let cols = self.arg_num(&args, 9, 14.)? as usize;
6830 let rows = self.arg_num(&args, 10, 10.)? as usize;
6831 let cell = self.arg_num(&args, 11, 0.6)? as f32;
6832 let amp = self.arg_num(&args, 12, 0.25)? as f32;
6833 let freq = self.arg_num(&args, 13, 4.)? as f32;
6834 let fr = self.arg_num(&args, 14, 0.)? as f32;
6835 let hue = self.arg_num(&args, 15, 0.)? as f32;
6836 let mut gfx = self.gfx.borrow_mut();
6837 let cam = gfx.camera.clone();
6838 crate::gfx::vtex::draw_tessellated(
6839 &mut gfx.depth_queue,
6840 &cam,
6841 cx,
6842 cy,
6843 cz,
6844 ux,
6845 uy,
6846 uz,
6847 vx,
6848 vy,
6849 vz,
6850 cols,
6851 rows,
6852 cell,
6853 amp,
6854 freq,
6855 fr,
6856 hue,
6857 );
6858 return Ok(Value::Unit);
6859 },
6860
6861 "vtex_lotus" | "ลายดอกบัว" | "纹莲" | "蓮模様" | "연꽃무늬" | "الگوی_لوتوس" | "نقش_لوتس" | "דוגמת_לוטוס" | "نقش_کنول" | "motif_lotus" | "muster_lotus" | "узор_лотос" =>
6863 {
6864 let cx = self.arg_num(&args, 0, 0.)? as f32;
6865 let cy = self.arg_num(&args, 1, 0.)? as f32;
6866 let cz = self.arg_num(&args, 2, 0.)? as f32;
6867 let ux = self.arg_num(&args, 3, 1.)? as f32;
6868 let uy = self.arg_num(&args, 4, 0.)? as f32;
6869 let uz = self.arg_num(&args, 5, 0.)? as f32;
6870 let vx = self.arg_num(&args, 6, 0.)? as f32;
6871 let vy = self.arg_num(&args, 7, 0.)? as f32;
6872 let vz = self.arg_num(&args, 8, 1.)? as f32;
6873 let ri = self.arg_num(&args, 9, 1.)? as f32;
6874 let ro = self.arg_num(&args, 10, 2.)? as f32;
6875 let np = self.arg_num(&args, 11, 12.)? as usize;
6876 let fr = self.arg_num(&args, 12, 0.)? as f32;
6877 let hue = self.arg_num(&args, 13, 0.)? as f32;
6878 let mut gfx = self.gfx.borrow_mut();
6879 let cam = gfx.camera.clone();
6880 crate::gfx::vtex::draw_lotus(
6881 &mut gfx.depth_queue,
6882 &cam,
6883 cx,
6884 cy,
6885 cz,
6886 ux,
6887 uy,
6888 uz,
6889 vx,
6890 vy,
6891 vz,
6892 ri,
6893 ro,
6894 np,
6895 fr,
6896 hue,
6897 );
6898 return Ok(Value::Unit);
6899 },
6900
6901 "vtex_chakra" | "ลายจักร" | "纹轮" | "輪模様" | "바퀴무늬" | "الگوی_چاکرا" | "نقش_تشاكرا" | "דוגמת_צ'אקרה" | "نقش_چکر" | "motif_chakra" | "muster_chakra" | "узор_чакра" => {
6903 let cx = self.arg_num(&args, 0, 0.)? as f32;
6904 let cy = self.arg_num(&args, 1, 0.)? as f32;
6905 let cz = self.arg_num(&args, 2, 0.)? as f32;
6906 let ux = self.arg_num(&args, 3, 1.)? as f32;
6907 let uy = self.arg_num(&args, 4, 0.)? as f32;
6908 let uz = self.arg_num(&args, 5, 0.)? as f32;
6909 let vx = self.arg_num(&args, 6, 0.)? as f32;
6910 let vy = self.arg_num(&args, 7, 0.)? as f32;
6911 let vz = self.arg_num(&args, 8, 1.)? as f32;
6912 let r = self.arg_num(&args, 9, 2.)? as f32;
6913 let ns = self.arg_num(&args, 10, 8.)? as usize;
6914 let fr = self.arg_num(&args, 11, 0.)? as f32;
6915 let hue = self.arg_num(&args, 12, 0.)? as f32;
6916 let mut gfx = self.gfx.borrow_mut();
6917 let cam = gfx.camera.clone();
6918 crate::gfx::vtex::draw_chakra(
6919 &mut gfx.depth_queue,
6920 &cam,
6921 cx,
6922 cy,
6923 cz,
6924 ux,
6925 uy,
6926 uz,
6927 vx,
6928 vy,
6929 vz,
6930 r,
6931 ns,
6932 fr,
6933 hue,
6934 );
6935 return Ok(Value::Unit);
6936 },
6937
6938 "vtex_yantra" | "ลายยันต์" | "纹咒" | "護符模様" | "부적무늬" | "الگوی_یانترا" | "نقش_يانترا" | "דוגמת_יאנטרה" | "نقش_ینترا" | "motif_yantra" | "muster_yantra" | "узор_янтра" =>
6940 {
6941 let cx = self.arg_num(&args, 0, 0.)? as f32;
6942 let cy = self.arg_num(&args, 1, 0.)? as f32;
6943 let cz = self.arg_num(&args, 2, 0.)? as f32;
6944 let ux = self.arg_num(&args, 3, 1.)? as f32;
6945 let uy = self.arg_num(&args, 4, 0.)? as f32;
6946 let uz = self.arg_num(&args, 5, 0.)? as f32;
6947 let vx = self.arg_num(&args, 6, 0.)? as f32;
6948 let vy = self.arg_num(&args, 7, 0.)? as f32;
6949 let vz = self.arg_num(&args, 8, 1.)? as f32;
6950 let nl = self.arg_num(&args, 9, 4.)? as usize;
6951 let mr = self.arg_num(&args, 10, 3.)? as f32;
6952 let fr = self.arg_num(&args, 11, 0.)? as f32;
6953 let hue = self.arg_num(&args, 12, 0.)? as f32;
6954 let mut gfx = self.gfx.borrow_mut();
6955 let cam = gfx.camera.clone();
6956 crate::gfx::vtex::draw_yantra(
6957 &mut gfx.depth_queue,
6958 &cam,
6959 cx,
6960 cy,
6961 cz,
6962 ux,
6963 uy,
6964 uz,
6965 vx,
6966 vy,
6967 vz,
6968 nl,
6969 mr,
6970 fr,
6971 hue,
6972 );
6973 return Ok(Value::Unit);
6974 },
6975
6976 "vtex_spiked_cog" | "ฟันเฟืองหนาม" | "纹棘轮" | "歯車模様" | "톱니바퀴" | "الگوی_چرخدنده_خاردار" | "نقش_ترس_شائك" | "דוגמת_גלגל_קוצני" | "نقش_خاردار_گیئر" | "motif_engrenage_pointes" | "muster_stachelzahnrad" | "узор_шестерня_шипы" =>
6978 {
6979 let cx = self.arg_num(&args, 0, 0.)? as f32;
6980 let cy = self.arg_num(&args, 1, 0.)? as f32;
6981 let cz = self.arg_num(&args, 2, 0.)? as f32;
6982 let ux = self.arg_num(&args, 3, 1.)? as f32;
6983 let uy = self.arg_num(&args, 4, 0.)? as f32;
6984 let uz = self.arg_num(&args, 5, 0.)? as f32;
6985 let vx = self.arg_num(&args, 6, 0.)? as f32;
6986 let vy = self.arg_num(&args, 7, 0.)? as f32;
6987 let vz = self.arg_num(&args, 8, 1.)? as f32;
6988 let nt = self.arg_num(&args, 9, 12.)? as usize;
6989 let rb = self.arg_num(&args, 10, 1.)? as f32;
6990 let rs = self.arg_num(&args, 11, 1.3)? as f32;
6991 let rh = self.arg_num(&args, 12, 0.2)? as f32;
6992 let ns = self.arg_num(&args, 13, 6.)? as usize;
6993 let fr = self.arg_num(&args, 14, 0.)? as f32;
6994 let hue = self.arg_num(&args, 15, 0.)? as f32;
6995 let mut gfx = self.gfx.borrow_mut();
6996 let cam = gfx.camera.clone();
6997 crate::gfx::vtex::draw_spiked_cog(
6998 &mut gfx.depth_queue,
6999 &cam,
7000 cx,
7001 cy,
7002 cz,
7003 ux,
7004 uy,
7005 uz,
7006 vx,
7007 vy,
7008 vz,
7009 nt,
7010 rb,
7011 rs,
7012 rh,
7013 ns,
7014 fr,
7015 hue,
7016 );
7017 return Ok(Value::Unit);
7018 },
7019
7020 "vtex_torii" | "ประตูโทริอิ" | "纹鸟居" | "鳥居" | "도리이" | "الگوی_توری_ژاپنی" | "نقش_توري" | "דוגמת_טוריי" | "نقش_توری_گیٹ" | "motif_torii" | "muster_torii" | "узор_тории" =>
7022 {
7023 let cx = self.arg_num(&args, 0, 0.)? as f32;
7024 let cy = self.arg_num(&args, 1, 0.)? as f32;
7025 let cz = self.arg_num(&args, 2, 0.)? as f32;
7026 let ux = self.arg_num(&args, 3, 1.)? as f32;
7027 let uy = self.arg_num(&args, 4, 0.)? as f32;
7028 let uz = self.arg_num(&args, 5, 0.)? as f32;
7029 let vx = self.arg_num(&args, 6, 0.)? as f32;
7030 let vy = self.arg_num(&args, 7, 0.)? as f32;
7031 let vz = self.arg_num(&args, 8, 1.)? as f32;
7032 let w = self.arg_num(&args, 9, 4.)? as f32;
7033 let h = self.arg_num(&args, 10, 5.)? as f32;
7034 let fr = self.arg_num(&args, 11, 0.)? as f32;
7035 let hue = self.arg_num(&args, 12, 0.)? as f32;
7036 let mut gfx = self.gfx.borrow_mut();
7037 let cam = gfx.camera.clone();
7038 crate::gfx::vtex::draw_torii(
7039 &mut gfx.depth_queue,
7040 &cam,
7041 cx,
7042 cy,
7043 cz,
7044 ux,
7045 uy,
7046 uz,
7047 vx,
7048 vy,
7049 vz,
7050 w,
7051 h,
7052 fr,
7053 hue,
7054 );
7055 return Ok(Value::Unit);
7056 },
7057
7058 "vtex_pagoda" | "เจดีย์" | "纹塔" | "塔" | "탑" | "الگوی_پاگودا" | "نقش_باغودا" | "דוגמת_פגודה" | "نقش_پگوڈا" | "motif_pagode" | "muster_pagode" | "узор_пагода" => {
7060 let cx = self.arg_num(&args, 0, 0.)? as f32;
7061 let cy = self.arg_num(&args, 1, 0.)? as f32;
7062 let cz = self.arg_num(&args, 2, 0.)? as f32;
7063 let ux = self.arg_num(&args, 3, 1.)? as f32;
7064 let uy = self.arg_num(&args, 4, 0.)? as f32;
7065 let uz = self.arg_num(&args, 5, 0.)? as f32;
7066 let vx = self.arg_num(&args, 6, 0.)? as f32;
7067 let vy = self.arg_num(&args, 7, 0.)? as f32;
7068 let vz = self.arg_num(&args, 8, 1.)? as f32;
7069 let nt = self.arg_num(&args, 9, 5.)? as usize;
7070 let bw = self.arg_num(&args, 10, 2.)? as f32;
7071 let th = self.arg_num(&args, 11, 1.)? as f32;
7072 let tp = self.arg_num(&args, 12, 0.72)? as f32;
7073 let eo = self.arg_num(&args, 13, 0.28)? as f32;
7074 let fr = self.arg_num(&args, 14, 0.)? as f32;
7075 let hue = self.arg_num(&args, 15, 0.)? as f32;
7076 let mut gfx = self.gfx.borrow_mut();
7077 let cam = gfx.camera.clone();
7078 crate::gfx::vtex::draw_pagoda(
7079 &mut gfx.depth_queue,
7080 &cam,
7081 cx,
7082 cy,
7083 cz,
7084 ux,
7085 uy,
7086 uz,
7087 vx,
7088 vy,
7089 vz,
7090 nt,
7091 bw,
7092 th,
7093 tp,
7094 eo,
7095 fr,
7096 hue,
7097 );
7098 return Ok(Value::Unit);
7099 },
7100
7101 #[cfg(not(target_arch = "wasm32"))]
7107 "audio_tone"
7108 | "เสียงโทน"
7109 | "音调"
7110 | "音調"
7111 | "음조"
7112 | "空间音"
7113 | "空間音"
7114 | "공간음" | "تن_صدا" | "نغمة" | "צליל" | "آواز_کا_سر" | "tonalité_audio" | "audioton" | "звук_тон" => {
7115 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7116 let x = self.arg_num(&args, 1, 0.0)? as f32;
7117 let y = self.arg_num(&args, 2, 0.0)? as f32;
7118 let z = self.arg_num(&args, 3, 0.0)? as f32;
7119 let w = self.arg_num(&args, 4, 1.0)? as f32;
7120 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7121 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7122 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7123 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7124 if let Some(audio) = &self.audio {
7125 audio.set_tone(
7126 idx,
7127 ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth },
7128 );
7129 }
7130 return Ok(Value::Unit);
7131 },
7132
7133 #[cfg(not(target_arch = "wasm32"))]
7134 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" | "شنونده_صدا" | "مستمع_الصوت" | "מאזין_קול" | "آواز_سننے_والا" | "auditeur_audio" | "audiohörer" | "звук_слушатель" =>
7135 {
7136 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7137 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7138 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7139 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7140 if let Some(audio) = &self.audio {
7141 audio.set_listener(cry, sry, crx, srx);
7142 }
7143 return Ok(Value::Unit);
7144 },
7145
7146 #[cfg(not(target_arch = "wasm32"))]
7147 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" | "موسیقی_پسزمینه" | "موسيقى_خلفية" | "מוזיקת_רקע" | "پس_منظر_موسیقی" | "musique_fond" | "hintergrundmusik" | "фоновая_музыка" =>
7148 {
7149 let path = match args.first() {
7150 Some(Value::Str(s)) => s.clone(),
7151 _ => return Ok(Value::Unit),
7152 };
7153 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7154 if let Some(audio) = &self.audio {
7155 audio.load_bgm(&path, vol);
7156 }
7157 return Ok(Value::Unit);
7158 },
7159
7160 #[cfg(not(target_arch = "wasm32"))]
7161 "audio_bgm_volume"
7162 | "ระดับเสียงพื้นหลัง"
7163 | "ระดับเพลงประกอบ"
7164 | "背景乐音量"
7165 | "BGM音量"
7166 | "배경음악음량" | "بلندی_موسیقی_پسزمینه" | "مستوى_موسيقى_الخلفية" | "עוצמת_מוזיקת_רקע" | "پس_منظر_موسیقی_شدت" | "volume_musique_fond" | "hintergrundmusiklautstärke" | "громкость_фоновой_музыки" => {
7167 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7168 if let Some(audio) = &self.audio {
7169 audio.set_bgm_volume(vol);
7170 }
7171 return Ok(Value::Unit);
7172 },
7173
7174 #[cfg(not(target_arch = "wasm32"))]
7175 "audio_volume" | "ระดับเสียง" | "音量" | "음량" | "بلندی_صدا" | "مستوى_الصوت" | "עוצמת_קול" | "آواز_کی_شدت" | "volume_audio" | "audiolautstärke" | "звук_громкость" => {
7176 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7177 if let Some(audio) = &self.audio {
7178 audio.set_master_volume(vol);
7179 }
7180 return Ok(Value::Unit);
7181 },
7182
7183 #[cfg(target_arch = "wasm32")]
7185 "audio_tone"
7186 | "เสียงโทน"
7187 | "音调"
7188 | "音調"
7189 | "음조"
7190 | "空间音"
7191 | "空間音"
7192 | "공간음" | "تن_صدا" | "نغمة" | "צליל" | "آواز_کا_سر" | "tonalité_audio" | "audioton" | "звук_тон" => {
7193 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7194 let x = self.arg_num(&args, 1, 0.0)? as f32;
7195 let y = self.arg_num(&args, 2, 0.0)? as f32;
7196 let z = self.arg_num(&args, 3, 0.0)? as f32;
7197 let w = self.arg_num(&args, 4, 1.0)? as f32;
7198 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7199 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7200 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7201 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7202 crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
7203 return Ok(Value::Unit);
7204 },
7205
7206 #[cfg(target_arch = "wasm32")]
7207 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" | "شنونده_صدا" | "مستمع_الصوت" | "מאזין_קול" | "آواز_سننے_والا" | "auditeur_audio" | "audiohörer" | "звук_слушатель" =>
7208 {
7209 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7210 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7211 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7212 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7213 crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
7214 return Ok(Value::Unit);
7215 },
7216
7217 #[cfg(target_arch = "wasm32")]
7218 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" | "موسیقی_پسزمینه" | "موسيقى_خلفية" | "מוזיקת_רקע" | "پس_منظر_موسیقی" | "musique_fond" | "hintergrundmusik" | "фоновая_музыка" =>
7219 {
7220 let path = self.arg_str(&args, 0, "");
7221 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7222 crate::gfx::audio_web::load_bgm(&path, vol);
7223 return Ok(Value::Unit);
7224 },
7225
7226 #[cfg(target_arch = "wasm32")]
7227 "audio_bgm_volume"
7228 | "ระดับเสียงพื้นหลัง"
7229 | "ระดับเพลงประกอบ"
7230 | "背景乐音量"
7231 | "BGM音量"
7232 | "배경음악음량" | "بلندی_موسیقی_پسزمینه" | "مستوى_موسيقى_الخلفية" | "עוצמת_מוזיקת_רקע" | "پس_منظر_موسیقی_شدت" | "volume_musique_fond" | "hintergrundmusiklautstärke" | "громкость_фоновой_музыки" => {
7233 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7234 crate::gfx::audio_web::set_bgm_volume(vol);
7235 return Ok(Value::Unit);
7236 },
7237
7238 #[cfg(target_arch = "wasm32")]
7239 "audio_volume" | "ระดับเสียง" | "音量" | "음량" | "بلندی_صدا" | "مستوى_الصوت" | "עוצמת_קול" | "آواز_کی_شدت" | "volume_audio" | "audiolautstärke" | "звук_громкость" => {
7240 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7241 crate::gfx::audio_web::set_master_volume(vol);
7242 return Ok(Value::Unit);
7243 },
7244
7245 #[cfg(target_arch = "wasm32")]
7247 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" | "بارگذاری_نمونه_صدا" | "تحميل_عينة_صوتية" | "טעינת_דגימת_קול" | "آواز_نمونہ_لوڈ" | "charger_échantillon" | "sample_laden" | "загрузить_семпл" =>
7248 {
7249 let path = self.arg_str(&args, 0, "");
7250 let resolved = self.wasm_resolve_source_path(&path);
7251 match wasm_fetch_bytes(&resolved)
7252 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
7253 {
7254 Ok(t) => {
7255 let id = crate::gfx::audio_web::add_sample(&t.stereo, t.channels, t.rate);
7256 return Ok(Value::Number(id as f64));
7257 },
7258 Err(e) => {
7259 eprintln!("audio_sample_load failed ({path}): {e}");
7260 return Ok(Value::Number(-1.0));
7261 },
7262 }
7263 },
7264 #[cfg(target_arch = "wasm32")]
7265 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" | "پخش_نمونه_صدا" | "تشغيل_عينة_صوتية" | "נגינת_דגימת_קול" | "آواز_نمونہ_چلاؤ" | "jouer_échantillon" | "sample_abspielen" | "играть_семпл" =>
7266 {
7267 let id = self.arg_num(&args, 0, 0.0)? as usize;
7268 let x = self.arg_num(&args, 1, 0.0)? as f32;
7269 let y = self.arg_num(&args, 2, 0.0)? as f32;
7270 let z = self.arg_num(&args, 3, 0.0)? as f32;
7271 let vol = self.arg_num(&args, 5, 1.0)? as f32;
7273 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
7274 crate::gfx::audio_web::play_sample(id, x, y, z, vol, looping);
7275 return Ok(Value::Number(0.0));
7276 },
7277 #[cfg(target_arch = "wasm32")]
7278 "audio_sample_stop"
7279 | "停止采样"
7280 | "サンプル停止"
7281 | "샘플정지"
7282 | "หยุดตัวอย่างเสียง"
7283 | "audio_fx_reverb"
7284 | "混响"
7285 | "リバーブ"
7286 | "리버브"
7287 | "เสียงก้อง"
7288 | "audio_fx_delay"
7289 | "回声"
7290 | "ディレイ効果"
7291 | "딜레이"
7292 | "เสียงสะท้อน"
7293 | "audio_fx_lowpass"
7294 | "低通滤波"
7295 | "ローパス"
7296 | "저역통과"
7297 | "กรองความถี่ต่ำ" | "توقف_نمونه_صدا" | "إيقاف_عينة_صوتية" | "עצירת_דגימת_קול" | "آواز_نمونہ_روکو" | "arrêter_échantillon" | "sample_stoppen" | "остановить_семпл" => {
7298 return Ok(Value::Unit);
7299 },
7300
7301 "รอหน้าต่าง" | "wait_window" | "gfx_wait" | "انتظار_پنجره" | "انتظر_النافذة" | "המתן_לחלון" | "ونڈو_انتظار" => {
7303 #[cfg(not(target_arch = "wasm32"))]
7304 loop {
7305 let still_open = {
7306 let gfx = self.gfx.borrow();
7307 gfx.window
7308 .as_ref()
7309 .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
7310 .unwrap_or(false)
7311 };
7312 if !still_open {
7313 break;
7314 }
7315 let (buf, w, h) = {
7316 let gfx = self.gfx.borrow();
7317 (gfx.buffer.clone(), gfx.width, gfx.height)
7318 };
7319 let mut gfx = self.gfx.borrow_mut();
7320 if let Some(win) = gfx.window.as_mut() {
7321 if win.update_with_buffer(&buf, w, h).is_err() {
7322 break;
7323 }
7324 }
7325 }
7326 return Ok(Value::Unit);
7327 },
7328
7329 "read_file" | "อ่านไฟล์" | "خواندن_فایل" | "اقرأ_الملف" | "קרא_קובץ" | "فائل_پڑھو" => {
7331 #[cfg(target_arch = "wasm32")]
7332 return Ok(Value::Str(String::new()));
7333 #[cfg(not(target_arch = "wasm32"))]
7334 {
7335 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7336 return std::fs::read_to_string(&path)
7337 .map(Value::Str)
7338 .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
7339 }
7340 },
7341 #[cfg(not(target_arch = "wasm32"))]
7343 "net_host" | "เน็ตโฮสต์" | "میزبانی_شبکه" | "استضف_الشبكة" | "ארח_רשת" | "نیٹ_ہوسٹ" => {
7344 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7345 net::host(port);
7346 return Ok(Value::Unit);
7347 },
7348 #[cfg(not(target_arch = "wasm32"))]
7349 "net_join" | "เน็ตจอย" | "پیوستن_شبکه" | "انضم_للشبكة" | "הצטרף_לרשת" | "نیٹ_شمولیت" => {
7350 let ip = self.arg_str(&args, 0, "127.0.0.1");
7351 let port = self.arg_num(&args, 1, 7777.0)? as u16;
7352 net::join(&ip, port);
7353 return Ok(Value::Unit);
7354 },
7355 #[cfg(not(target_arch = "wasm32"))]
7356 "net_send" | "เน็ตส่ง" | "ارسال_شبکه" | "أرسل_عبر_الشبكة" | "שלח_ברשת" | "نیٹ_بھیجو" => {
7357 let s = self.arg_str(&args, 0, "");
7358 net::send(&s);
7359 return Ok(Value::Unit);
7360 },
7361 #[cfg(not(target_arch = "wasm32"))]
7362 "net_recv" | "เน็ตรับ" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" => {
7363 return Ok(Value::Str(net::recv()));
7364 },
7365 #[cfg(not(target_arch = "wasm32"))]
7366 "net_status" | "เน็ตสถานะ" | "وضعیت_شبکه" | "حالة_الشبكة" | "סטטוס_רשת" | "نیٹ_حالت" => {
7367 return Ok(Value::Number(net::status() as f64));
7368 },
7369 #[cfg(not(target_arch = "wasm32"))]
7370 "net_recv_from" => {
7371 return Ok(Value::Str(net::recv_from()));
7372 },
7373 #[cfg(not(target_arch = "wasm32"))]
7374 "net_send_to" => {
7375 let id = self.arg_num(&args, 0, 0.0)? as u64;
7376 let s = self.arg_str(&args, 1, "");
7377 net::send_to(id, &s);
7378 return Ok(Value::Unit);
7379 },
7380 #[cfg(not(target_arch = "wasm32"))]
7381 "net_close" | "연결종료" => {
7382 net::close();
7383 return Ok(Value::Unit);
7384 },
7385 #[cfg(not(target_arch = "wasm32"))]
7387 "net_announce" | "เน็ตประกาศ" | "اعلام_شبکه" | "أعلن_في_الشبكة" | "הכרז_ברשת" | "نیٹ_اعلان" => {
7388 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7389 let info = self.arg_str(&args, 1, "");
7390 net::announce(port, &info);
7391 return Ok(Value::Unit);
7392 },
7393 #[cfg(not(target_arch = "wasm32"))]
7394 "net_announce_stop" | "เน็ตหยุดประกาศ" | "توقف_اعلام" | "أوقف_الإعلان" | "עצור_הכרזה" | "اعلان_روکو" => {
7395 net::announce_stop();
7396 return Ok(Value::Unit);
7397 },
7398 #[cfg(not(target_arch = "wasm32"))]
7399 "net_discover" | "เน็ตค้นหา" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" => {
7400 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7401 return Ok(Value::Str(net::discover(port)));
7402 },
7403 #[cfg(not(target_arch = "wasm32"))]
7404 "net_test" | "เน็ตทดสอบ" | "آزمون_شبکه" | "اختبر_الشبكة" | "בדוק_רשת" | "نیٹ_ٹیسٹ" => {
7405 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7406 return Ok(Value::Str(net::test_bind(port)));
7407 },
7408 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7410 "http_route" | "เว็บเส้นทาง" | "مسیر_HTTP" | "مسار_HTTP" | "נתיב_HTTP" | "HTTP_روٹ" => {
7411 let method = self.arg_str(&args, 0, "GET").to_uppercase();
7412 let path = self.arg_str(&args, 1, "/");
7413 let handler = args.get(2).cloned().unwrap_or(Value::Unit);
7414 self.http_routes.push((method, path, handler));
7415 return Ok(Value::Unit);
7416 },
7417 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7421 "http_static" | "เว็บสแตติก" | "فایل_ایستای_HTTP" | "ملفات_HTTP_ثابتة" | "קבצים_סטטיים_HTTP" | "HTTP_مستقل_فائل" => {
7422 let prefix = self.arg_str(&args, 0, "/static");
7423 let dir = self.arg_str(&args, 1, "static");
7424 self.http_static_dirs.push((prefix, dir));
7425 return Ok(Value::Unit);
7426 },
7427 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7428 "http_serve" | "เว็บเสิร์ฟ" | "سرویس_HTTP" | "قدّم_HTTP" | "הגש_HTTP" | "HTTP_سرو" => {
7429 let host = self.arg_str(&args, 0, "127.0.0.1");
7430 let port = self.arg_num(&args, 1, 8080.0)? as u16;
7431 let routes = std::mem::take(&mut self.http_routes);
7432 let static_dirs = std::mem::take(&mut self.http_static_dirs);
7433 let rx = web::spawn_server(host.clone(), port, static_dirs);
7439 for pending in rx {
7440 let matched = routes
7441 .iter()
7442 .find(|(m, p, _)| m == &pending.method && p == &pending.path);
7443 let response = match matched {
7444 Some((_, _, handler)) => {
7445 let req_value = Value::Struct {
7446 name: "Request".to_string(),
7447 fields: vec![
7448 ("method".to_string(), Value::Str(pending.method.clone())),
7449 ("path".to_string(), Value::Str(pending.path.clone())),
7450 ("query".to_string(), Value::Str(pending.query.clone())),
7451 ("body".to_string(), Value::Str(pending.body.clone())),
7452 ("cookie".to_string(), Value::Str(pending.cookie.clone())),
7453 (
7454 "authorization".to_string(),
7455 Value::Str(pending.authorization.clone()),
7456 ),
7457 (
7458 "client_ip".to_string(),
7459 Value::Str(pending.client_ip.clone()),
7460 ),
7461 ],
7462 };
7463 match self.call_value(handler.clone(), vec![req_value]) {
7464 Ok(v) => web::value_to_response(&v),
7465 Err(e) => web::HttpResponse {
7466 status: 500,
7467 content_type: "text/plain; charset=utf-8".to_string(),
7468 body: format!("handler error: {e:?}"),
7469 set_cookie: None,
7470 location: None,
7471 },
7472 }
7473 },
7474 None => web::HttpResponse {
7475 status: 404,
7476 content_type: "text/plain; charset=utf-8".to_string(),
7477 body: "not found".to_string(),
7478 set_cookie: None,
7479 location: None,
7480 },
7481 };
7482 let _ = pending.respond_to.send(response);
7483 }
7484 return Ok(Value::Unit);
7485 },
7486 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7490 "http_post_async" | "เว็บโพสต์ไม่บล็อก" | "ارسال_ناهمگام_HTTP" | "أرسل_HTTP_غير_متزامن" | "שלח_HTTP_אסינכרוני" | "HTTP_غیر_ہمزمان_بھیجو" => {
7491 let url = self.arg_str(&args, 0, "");
7492 let body = self.arg_str(&args, 1, "");
7493 let content_type = self.arg_str(&args, 2, "application/json");
7494 let id = self.async_jobs.start_post(url, content_type, body);
7495 return Ok(Value::Str(id));
7496 },
7497 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7501 "http_job_poll" | "เว็บงานสำรวจ" | "بررسی_وظیفه_HTTP" | "استطلع_مهمة_HTTP" | "בדוק_משימת_HTTP" | "HTTP_کام_پول" => {
7502 let id = self.arg_str(&args, 0, "");
7503 return Ok(Value::Str(self.async_jobs.poll(&id).unwrap_or_default()));
7504 },
7505 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7512 "sdai_generate_start" | "เอสดีเอไอเริ่มสร้าง" | "شروع_تولید_هوش" | "ابدأ_توليد_الذكاء" | "התחל_יצירת_בינה" | "اے_آئی_تخلیق_شروع" => {
7513 let base_url = self.arg_str(&args, 0, "http://127.0.0.1:1342");
7514 let prompt = self.arg_str(&args, 1, "");
7515 let width = self.arg_num(&args, 2, 512.0)? as u32;
7516 let height = self.arg_num(&args, 3, 512.0)? as u32;
7517 let id = self.async_jobs.start_sdai_txt2img(base_url, prompt, width, height);
7518 return Ok(Value::Str(id));
7519 },
7520 "query_param" | "พารามิเตอร์" | "پارامتر_پرسوجو" | "معامل_الاستعلام" | "פרמטר_שאילתה" | "کوئری_پیرامیٹر" => {
7522 let qs = self.arg_str(&args, 0, "");
7523 let name = self.arg_str(&args, 1, "");
7524 let default = self.arg_str(&args, 2, "");
7525 let mut found = default;
7526 for pair in qs.split('&') {
7527 let mut it = pair.splitn(2, '=');
7528 if it.next().unwrap_or("") == name {
7529 found = url_decode(it.next().unwrap_or(""));
7530 break;
7531 }
7532 }
7533 return Ok(Value::Str(found));
7534 },
7535 "cookie_get" | "รับคุกกี้" | "دریافت_کوکی" | "اجلب_الكعكة" | "קבל_עוגייה" | "کوکی_حاصل_کرو" => {
7537 let header = self.arg_str(&args, 0, "");
7538 let name = self.arg_str(&args, 1, "");
7539 let default = self.arg_str(&args, 2, "");
7540 let mut found = default;
7541 for pair in header.split(';') {
7542 let p = pair.trim();
7543 let mut it = p.splitn(2, '=');
7544 if it.next().unwrap_or("") == name {
7545 found = it.next().unwrap_or("").to_string();
7546 break;
7547 }
7548 }
7549 return Ok(Value::Str(found));
7550 },
7551 "html_escape" | "กันเอชทีเอ็มแอล" | "فرار_HTML" | "أفلت_HTML" | "בריחת_HTML" | "HTML_ایسکیپ" => {
7553 let s = self.arg_str(&args, 0, "");
7554 return Ok(Value::Str(
7555 s.replace('&', "&")
7556 .replace('<', "<")
7557 .replace('>', ">")
7558 .replace('"', """)
7559 .replace('\'', "'"),
7560 ));
7561 },
7562 "json_escape" | "หนีเจสัน" | "فرار_JSON" | "أفلت_JSON" | "בריחת_JSON" | "JSON_ایسکیپ" => {
7567 let s = self.arg_str(&args, 0, "");
7568 let mut out = String::with_capacity(s.len() + 8);
7569 for c in s.chars() {
7570 match c {
7571 '"' => out.push_str("\\\""),
7572 '\\' => out.push_str("\\\\"),
7573 '\n' => out.push_str("\\n"),
7574 '\r' => out.push_str("\\r"),
7575 '\t' => out.push_str("\\t"),
7576 c if (c as u32) < 0x20 => {
7577 out.push_str(&format!("\\u{:04x}", c as u32))
7578 },
7579 c => out.push(c),
7580 }
7581 }
7582 return Ok(Value::Str(out));
7583 },
7584 "cli_arg" | "อาร์กิวเมนต์" | "آرگومان_خطفرمان" | "معامل_سطر_الأوامر" | "ארגומנט_שורת_פקודה" | "سی_ایل_آئی_دلیل" => {
7586 let name = self.arg_str(&args, 0, "");
7587 let default = self.arg_str(&args, 1, "");
7588 let flag = format!("--{name}");
7589 let argv: Vec<String> = std::env::args().collect();
7590 let found = argv
7591 .iter()
7592 .position(|a| a == &flag)
7593 .and_then(|i| argv.get(i + 1))
7594 .cloned()
7595 .unwrap_or(default);
7596 return Ok(Value::Str(found));
7597 },
7598 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7600 "db_open" | "ฐานข้อมูลเปิด" | "باز_کردن_پایگاه_داده" | "افتح_قاعدة_البيانات" | "פתח_מסד_נתונים" | "ڈیٹا_بیس_کھولو" => {
7601 let path = self.arg_str(&args, 0, "app.db");
7602 let conn = ling_http::rusqlite::Connection::open(&path)
7603 .map_err(|e| EvalErr::from(format!("db_open '{path}': {e}")))?;
7604 let _ = conn.execute_batch("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;");
7605 self.db = Some(conn);
7606 return Ok(Value::Unit);
7607 },
7608 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7611 "db_exec" | "ฐานข้อมูลรัน" | "اجرای_پایگاه_داده" | "نفّذ_في_قاعدة_البيانات" | "בצע_במסד_נתונים" | "ڈیٹا_بیس_عمل" => {
7612 let sql = self.arg_str(&args, 0, "");
7613 let params = values_to_sql_params(&args[1.min(args.len())..]);
7614 let conn = self
7615 .db
7616 .as_ref()
7617 .ok_or_else(|| EvalErr::from("db_exec: call db_open first".to_string()))?;
7618 let n = conn
7619 .execute(
7620 &sql,
7621 ling_http::rusqlite::params_from_iter(params.iter()),
7622 )
7623 .map_err(|e| EvalErr::from(format!("db_exec: {e}\n sql: {sql}")))?;
7624 return Ok(Value::Number(n as f64));
7625 },
7626 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7628 "db_query" | "ฐานข้อมูลถาม" | "پرسوجوی_پایگاه_داده" | "استعلم_قاعدة_البيانات" | "שאילתת_מסד_נתונים" | "ڈیٹا_بیس_سوال" => {
7629 let sql = self.arg_str(&args, 0, "");
7630 let params = values_to_sql_params(&args[1.min(args.len())..]);
7631 let conn = self
7632 .db
7633 .as_ref()
7634 .ok_or_else(|| EvalErr::from("db_query: call db_open first".to_string()))?;
7635 let mut stmt = conn
7636 .prepare(&sql)
7637 .map_err(|e| EvalErr::from(format!("db_query: {e}\n sql: {sql}")))?;
7638 let col_names: Vec<String> =
7639 stmt.column_names().iter().map(|s| s.to_string()).collect();
7640 let mut rows = stmt
7641 .query(ling_http::rusqlite::params_from_iter(params.iter()))
7642 .map_err(|e| EvalErr::from(format!("db_query: {e}")))?;
7643 let mut out = Vec::new();
7644 while let Some(row) = rows
7645 .next()
7646 .map_err(|e| EvalErr::from(format!("db_query row: {e}")))?
7647 {
7648 let mut fields = Vec::with_capacity(col_names.len());
7649 for (i, col) in col_names.iter().enumerate() {
7650 use ling_http::rusqlite::types::ValueRef;
7651 let v = match row.get_ref(i) {
7652 Ok(ValueRef::Null) => Value::Str(String::new()),
7653 Ok(ValueRef::Integer(n)) => Value::Number(n as f64),
7654 Ok(ValueRef::Real(n)) => Value::Number(n),
7655 Ok(ValueRef::Text(t)) => {
7656 Value::Str(String::from_utf8_lossy(t).into_owned())
7657 },
7658 Ok(ValueRef::Blob(b)) =>
7659
7660 {
7661 use base64::Engine as _;
7662 Value::Str(base64::engine::general_purpose::STANDARD.encode(b))
7663 },
7664 Err(_) => Value::Str(String::new()),
7665 };
7666 fields.push((col.clone(), v));
7667 }
7668 out.push(Value::Struct { name: "Row".to_string(), fields });
7669 }
7670 return Ok(Value::List(Rc::new(out)));
7671 },
7672 #[cfg(not(target_arch = "wasm32"))]
7674 "gamepad_poll" | "จอยโพล" | "بررسی_دسته_بازی" | "استطلع_يد_اللعب" | "בדוק_בקר_משחק" | "گیم_پیڈ_پول_کرو" => {
7675 gamepad::poll();
7676 return Ok(Value::Unit);
7677 },
7678 #[cfg(not(target_arch = "wasm32"))]
7679 "gamepad_button" | "จอยปุ่ม" | "دکمه_دسته_بازی" | "زر_يد_اللعب" | "כפתור_בקר_משחק" | "گیم_پیڈ_بٹن_دبایا" => {
7680 let name = self.arg_str(&args, 0, "");
7681 return Ok(Value::Number(if gamepad::button(&name) {
7682 1.0
7683 } else {
7684 0.0
7685 }));
7686 },
7687 #[cfg(not(target_arch = "wasm32"))]
7688 "gamepad_axis" | "จอยแกน" | "محور_دسته_بازی" | "محور_يد_اللعب" | "ציר_בקר_משחק" | "گیم_پیڈ_محور" => {
7689 let name = self.arg_str(&args, 0, "");
7690 return Ok(Value::Number(gamepad::axis(&name) as f64));
7691 },
7692 #[cfg(not(target_arch = "wasm32"))]
7693 "gamepad_rumble" | "จอยสั่น" | "لرزش_دسته_بازی" | "اهتزاز_يد_اللعب" | "רטט_בקר_משחק" | "گیم_پیڈ_لرزش" => {
7694 let low = self.arg_num(&args, 0, 0.0)? as f32;
7695 let high = self.arg_num(&args, 1, 0.0)? as f32;
7696 let ms = self.arg_num(&args, 2, 200.0)? as u32;
7697 gamepad::rumble(low, high, ms);
7698 return Ok(Value::Unit);
7699 },
7700 #[cfg(not(target_arch = "wasm32"))]
7701 "gamepad_list" | "จอยรายการ" | "فهرست_دستههای_بازی" | "قائمة_أيدي_اللعب" | "רשימת_בקרי_משחק" | "گیم_پیڈ_فہرست" => {
7702 return Ok(Value::Str(gamepad::list()));
7703 },
7704 #[cfg(not(target_arch = "wasm32"))]
7705 "gamepad_any" | "จอยใดๆ" | "هر_دسته_بازی" | "أي_يد_لعب" | "בקר_כלשהו" | "کوئی_بھی_گیم_پیڈ" => {
7706 return Ok(Value::Number(if gamepad::any_button() { 1.0 } else { 0.0 }));
7707 },
7708 #[cfg(target_arch = "wasm32")]
7710 "gamepad_poll" | "จอยโพล" | "gamepad_rumble" | "จอยสั่น" | "بررسی_دسته_بازی" | "استطلع_يد_اللعب" | "בדוק_בקר_משחק" | "گیم_پیڈ_پول_کرو" => {
7711 return Ok(Value::Unit);
7712 },
7713 #[cfg(target_arch = "wasm32")]
7714 "gamepad_button" | "จอยปุ่ม" | "gamepad_axis" | "จอยแกน" | "gamepad_any" | "จอยใดๆ" | "دکمه_دسته_بازی" | "زر_يد_اللعب" | "כפתור_בקר_משחק" | "گیم_پیڈ_بٹن" =>
7715 {
7716 return Ok(Value::Number(0.0));
7717 },
7718 #[cfg(target_arch = "wasm32")]
7719 "gamepad_list" | "จอยรายการ" | "فهرست_دستههای_بازی" | "قائمة_أيدي_اللعب" | "רשימת_בקרי_משחק" | "گیم_پیڈ_فہرست" => {
7720 return Ok(Value::Str(String::new()));
7721 },
7722
7723 #[cfg(not(target_arch = "wasm32"))]
7726 "nn_new" | "建神经网" | "ニューラル作成" | "신경망생성" | "สร้างโครงข่าย" | "شبکه_جدید" | "شبكة_جديدة" | "רשת_חדשה" | "نئی_نیورل_نیٹ" | "nouveau_réseau" | "neues_netz" | "новая_сеть" =>
7727 {
7728 let n_in = self.arg_num(&args, 0, 1.0)?.max(0.0) as usize;
7729 let seed = self.arg_num(&args, 1, 1.0)? as u64;
7730 return Ok(Value::Number(ai::nn_new(n_in, seed) as f64));
7731 },
7732 #[cfg(not(target_arch = "wasm32"))]
7734 "nn_dense" | "密集层" | "密層追加" | "밀집층" | "ชั้นหนาแน่น" | "لایه_متراکم" | "طبقة_كثيفة" | "שכבה_צפופה" | "ڈینس_لیئر" | "réseau_dense" | "netz_dicht" | "плотная_сеть" =>
7735 {
7736 let id = self.arg_num(&args, 0, -1.0)? as i64;
7737 let units = self.arg_num(&args, 1, 1.0)?.max(1.0) as usize;
7738 let act = self.arg_str(&args, 2, "relu");
7739 ai::nn_dense(id, units, &act);
7740 return Ok(Value::Unit);
7741 },
7742 #[cfg(not(target_arch = "wasm32"))]
7744 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" | "پیشروی_شبکه" | "تمرير_أمامي" | "העברה_קדימה" | "فارورڈ_پاس" | "propager_réseau" | "netz_vorwärts" | "прямой_проход_сети" =>
7745 {
7746 let id = self.arg_num(&args, 0, -1.0)? as i64;
7747 let input = self.arg_list_f32(&args, 1);
7748 let out = ai::nn_forward(id, &input);
7749 return Ok(Value::List(Rc::new(
7750 out.into_iter().map(|v| Value::Number(v as f64)).collect(),
7751 )));
7752 },
7753 #[cfg(not(target_arch = "wasm32"))]
7755 "nn_train" | "训练网" | "ニューラル学習" | "신경망학습" | "ฝึกโครงข่าย" | "آموزش_شبکه" | "درّب_الشبكة" | "אמן_רשת" | "نیٹ_ٹریننگ" | "entraîner_réseau" | "netz_trainieren" | "обучить_сеть" =>
7756 {
7757 let id = self.arg_num(&args, 0, -1.0)? as i64;
7758 let input = self.arg_list_f32(&args, 1);
7759 let target = self.arg_list_f32(&args, 2);
7760 let lr = self.arg_num(&args, 3, 0.01)? as f32;
7761 return Ok(Value::Number(ai::nn_train(id, &input, &target, lr) as f64));
7762 },
7763 #[cfg(not(target_arch = "wasm32"))]
7765 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" | "ذخیره_شبکه" | "احفظ_الشبكة" | "שמור_רשת" | "نیٹ_محفوظ_کرو" | "sauvegarder_réseau" | "netz_speichern" | "сохранить_сеть" =>
7766 {
7767 let id = self.arg_num(&args, 0, -1.0)? as i64;
7768 let path = self.arg_str(&args, 1, "model.lnn");
7769 return Ok(Value::Bool(ai::nn_save(id, &path)));
7770 },
7771 #[cfg(not(target_arch = "wasm32"))]
7773 "nn_load" | "载入网" | "網読込" | "신경망불러오기" | "โหลดโครงข่าย" | "بارگذاری_شبکه" | "حمّل_الشبكة" | "טען_רשת" | "نیٹ_لوڈ" | "charger_réseau" | "netz_laden" | "загрузить_сеть" =>
7774 {
7775 let path = self.arg_str(&args, 0, "model.lnn");
7776 return Ok(Value::Number(ai::nn_load(&path) as f64));
7777 },
7778
7779 #[cfg(not(target_arch = "wasm32"))]
7782 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" | "ساخت_درخت_رفتار" | "ابنِ_شجرة_السلوك" | "בנה_עץ_התנהגות" | "بی_ٹی_تعمیر" | "construire_arbre_comportement" | "verhaltensbaum_bauen" | "построить_дерево_поведения" =>
7783 {
7784 let spec = self.arg_str(&args, 0, "");
7785 return Ok(Value::Number(ai::bt_build(&spec) as f64));
7786 },
7787 #[cfg(not(target_arch = "wasm32"))]
7789 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" | "تنظیم_واقعیت" | "عيّن_حقيقة" | "קבע_עובדה" | "بی_ٹی_سیٹ" | "définir_arbre_comportement" | "verhaltensbaum_setzen" | "задать_дерево_поведения" =>
7790 {
7791 let id = self.arg_num(&args, 0, -1.0)? as i64;
7792 let key = self.arg_str(&args, 1, "");
7793 let val = self.arg_num(&args, 2, 0.0)? as f32;
7794 ai::bt_set(id, &key, val);
7795 return Ok(Value::Unit);
7796 },
7797 #[cfg(not(target_arch = "wasm32"))]
7799 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" | "تیک_درخت_رفتار" | "نبضة_شجرة_السلوك" | "טיק_עץ_התנהגות" | "بی_ٹی_ٹک" | "tick_arbre_comportement" | "verhaltensbaum_tick" | "тик_дерева_поведения" =>
7800 {
7801 let id = self.arg_num(&args, 0, -1.0)? as i64;
7802 return Ok(Value::Str(ai::bt_tick(id)));
7803 },
7804 #[cfg(not(target_arch = "wasm32"))]
7806 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" | "وضعیت_درخت_رفتار" | "حالة_شجرة_السلوك" | "סטטוס_עץ_התנהגות" | "بی_ٹی_حالت" | "statut_arbre_comportement" | "verhaltensbaum_status" | "статус_дерева_поведения" =>
7807 {
7808 let id = self.arg_num(&args, 0, -1.0)? as i64;
7809 return Ok(Value::Number(ai::bt_status(id) as f64));
7810 },
7811
7812 #[cfg(not(target_arch = "wasm32"))]
7815 "dialog_new" | "建对话模型" | "対話モデル作成" | "대화모델생성" | "สร้างโมเดลสนทนา" | "مدل_گفتگوی_جدید" | "نموذج_حوار_جديد" | "מודל_דיאלוג_חדש" | "نیا_مکالمہ_ماڈل" | "nouveau_dialogue" | "neuer_dialog" | "новый_диалог" =>
7816 {
7817 let ctx = self.arg_num(&args, 0, 3.0)?.max(1.0) as usize;
7818 let embed = self.arg_num(&args, 1, 32.0)?.max(1.0) as usize;
7819 let hidden = self.arg_num(&args, 2, 64.0)?.max(1.0) as usize;
7820 let seed = self.arg_num(&args, 3, 1.0)? as u64;
7821 return Ok(Value::Number(
7822 ai::dialog_new(ctx, embed, hidden, seed) as f64
7823 ));
7824 },
7825 #[cfg(not(target_arch = "wasm32"))]
7827 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" | "یادگیری_گفتگو" | "تعلّم_الحوار" | "למד_דיאלוג" | "مکالمہ_سیکھو" | "apprendre_dialogue" | "dialog_lernen" | "обучить_диалог" =>
7828 {
7829 let id = self.arg_num(&args, 0, -1.0)? as i64;
7830 let text = self.arg_str(&args, 1, "");
7831 ai::dialog_learn(id, &text);
7832 return Ok(Value::Unit);
7833 },
7834 #[cfg(not(target_arch = "wasm32"))]
7836 "dialog_load" | "对话载入" | "対話読込" | "대화불러오기" | "โหลดชุดสนทนา" | "بارگذاری_مجموعه_گفتگو" | "حمّل_مجموعة_الحوار" | "טען_מערך_דיאלוג" | "مکالمہ_مجموعہ_لوڈ" | "charger_dialogue" | "dialog_laden" | "загрузить_диалог" =>
7837 {
7838 let id = self.arg_num(&args, 0, -1.0)? as i64;
7839 let path = self.arg_str(&args, 1, "");
7840 return Ok(Value::Number(ai::dialog_load(id, &path) as f64));
7841 },
7842 #[cfg(not(target_arch = "wasm32"))]
7844 "dialog_train" | "对话训练" | "対話訓練" | "대화훈련" | "ฝึกสนทนา" | "آموزش_گفتگو" | "درّب_الحوار" | "אמן_דיאלוג" | "مکالمہ_ٹریننگ" | "entraîner_dialogue" | "dialog_trainieren" | "тренировать_диалог" =>
7845 {
7846 let id = self.arg_num(&args, 0, -1.0)? as i64;
7847 let epochs = self.arg_num(&args, 1, 20.0)?.max(1.0) as usize;
7848 let lr = self.arg_num(&args, 2, 0.1)? as f32;
7849 return Ok(Value::Number(ai::dialog_train(id, epochs, lr) as f64));
7850 },
7851 #[cfg(not(target_arch = "wasm32"))]
7853 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" | "بگو" | "قل" | "אמור" | "کہو" | "dire_dialogue" | "dialog_sagen" | "сказать_диалог" =>
7854 {
7855 let id = self.arg_num(&args, 0, -1.0)? as i64;
7856 let prompt = self.arg_str(&args, 1, "");
7857 let max = self.arg_num(&args, 2, 24.0)?.max(1.0) as usize;
7858 let temp = self.arg_num(&args, 3, 0.8)? as f32;
7859 return Ok(Value::Str(ai::dialog_say(id, &prompt, max, temp)));
7860 },
7861 #[cfg(not(target_arch = "wasm32"))]
7863 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" | "ذخیره_مدل_گفتگو" | "احفظ_نموذج_الحوار" | "שמור_מודל_דיאלוג" | "مکالمہ_ماڈل_محفوظ" | "sauvegarder_dialogue" | "dialog_speichern" | "сохранить_диалог" =>
7864 {
7865 let id = self.arg_num(&args, 0, -1.0)? as i64;
7866 let path = self.arg_str(&args, 1, "model.llm");
7867 return Ok(Value::Bool(ai::dialog_save(id, &path)));
7868 },
7869 #[cfg(not(target_arch = "wasm32"))]
7871 "dialog_load_model"
7872 | "对话载模"
7873 | "対話モデル読込"
7874 | "대화모델불러오기"
7875 | "โหลดโมเดลสนทนา" | "بارگذاری_مدل_گفتگو" | "حمّل_نموذج_الحوار" | "טען_מודל_דיאלוג" | "مکالمہ_ماڈل_لوڈ" | "charger_modèle_dialogue" | "dialog_modell_laden" | "загрузить_модель_диалога" => {
7876 let path = self.arg_str(&args, 0, "model.llm");
7877 return Ok(Value::Number(ai::dialog_load_model(&path) as f64));
7878 },
7879
7880 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7887 "llm_load" => {
7888 let gguf_path = self.arg_str(&args, 0, "");
7889 let tokenizer_path = self.arg_str(&args, 1, "");
7890 let device_index = self.arg_num(&args, 2, 0.0)? as i64;
7891 return Ok(Value::Number(
7892 llm::llm_load(&gguf_path, &tokenizer_path, device_index) as f64
7893 ));
7894 },
7895 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7897 "llm_generate_start" => {
7898 let handle = self.arg_num(&args, 0, -1.0)? as i64;
7899 let system_prompt = self.arg_str(&args, 1, "");
7900 let history = self.arg_str(&args, 2, "");
7901 let max_tokens = self.arg_num(&args, 3, 512.0)? as i64;
7902 let temperature = self.arg_num(&args, 4, 0.7)?;
7903 let top_p = self.arg_num(&args, 5, 0.9)?;
7904 return Ok(Value::Str(llm::llm_generate_start(
7905 handle, &system_prompt, &history, max_tokens, temperature, top_p,
7906 )));
7907 },
7908 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7910 "llm_generate_poll" => {
7911 let job_id = self.arg_str(&args, 0, "");
7912 return Ok(Value::Str(llm::llm_generate_poll(&job_id)));
7913 },
7914 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7916 "llm_generate_done" => {
7917 let job_id = self.arg_str(&args, 0, "");
7918 return Ok(Value::Bool(llm::llm_generate_done(&job_id)));
7919 },
7920 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7922 "llm_job_status" => {
7923 let job_id = self.arg_str(&args, 0, "");
7924 return Ok(Value::Str(llm::llm_job_status(&job_id)));
7925 },
7926 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7928 "llm_job_tool_name" => {
7929 let job_id = self.arg_str(&args, 0, "");
7930 return Ok(Value::Str(llm::llm_job_tool_name(&job_id)));
7931 },
7932 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7934 "llm_job_provide_tool_result" => {
7935 let job_id = self.arg_str(&args, 0, "");
7936 let result_text = self.arg_str(&args, 1, "");
7937 return Ok(Value::Bool(llm::llm_job_provide_tool_result(&job_id, &result_text)));
7938 },
7939 #[cfg(all(not(target_arch = "wasm32"), feature = "llm"))]
7941 "llm_job_cancel" => {
7942 let job_id = self.arg_str(&args, 0, "");
7943 llm::llm_job_cancel(&job_id);
7944 return Ok(Value::Unit);
7945 },
7946
7947 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7953 "oauth_google_start" => {
7954 let code = self.arg_str(&args, 0, "");
7955 let client_id = self.arg_str(&args, 1, "");
7956 let client_secret = self.arg_str(&args, 2, "");
7957 let redirect_uri = self.arg_str(&args, 3, "");
7958 let id = self.oauth_jobs.start_google_login(code, client_id, client_secret, redirect_uri);
7959 return Ok(Value::Str(id));
7960 },
7961 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7962 "oauth_google_done" => {
7963 let id = self.arg_str(&args, 0, "");
7964 return Ok(Value::Bool(self.oauth_jobs.done(&id)));
7965 },
7966 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7968 "oauth_google_error" => {
7969 let id = self.arg_str(&args, 0, "");
7970 return Ok(Value::Str(self.oauth_jobs.error(&id)));
7971 },
7972 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7973 "oauth_google_sub" => {
7974 let id = self.arg_str(&args, 0, "");
7975 return Ok(Value::Str(self.oauth_jobs.sub(&id)));
7976 },
7977 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7978 "oauth_google_email" => {
7979 let id = self.arg_str(&args, 0, "");
7980 return Ok(Value::Str(self.oauth_jobs.email(&id)));
7981 },
7982 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7983 "oauth_google_name" => {
7984 let id = self.arg_str(&args, 0, "");
7985 return Ok(Value::Str(self.oauth_jobs.name(&id)));
7986 },
7987
7988 #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
7992 "vision_load" => {
7993 let gguf_path = self.arg_str(&args, 0, "");
7994 let tokenizer_path = self.arg_str(&args, 1, "");
7995 let device_index = self.arg_num(&args, 2, 0.0)? as i64;
7996 return Ok(Value::Number(
7997 vision::vision_load(&gguf_path, &tokenizer_path, device_index) as f64
7998 ));
7999 },
8000 #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
8002 "vision_analyze_start" => {
8003 let handle = self.arg_num(&args, 0, -1.0)? as i64;
8004 let image_base64 = self.arg_str(&args, 1, "");
8005 let question = self.arg_str(&args, 2, "Describe this image.");
8006 let max_tokens = self.arg_num(&args, 3, 256.0)? as i64;
8007 let temperature = self.arg_num(&args, 4, 0.7)?;
8008 let top_p = self.arg_num(&args, 5, 0.9)?;
8009 return Ok(Value::Str(vision::vision_analyze_start(
8010 handle, &image_base64, &question, max_tokens, temperature, top_p,
8011 )));
8012 },
8013 #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
8014 "vision_analyze_poll" => {
8015 let job_id = self.arg_str(&args, 0, "");
8016 return Ok(Value::Str(vision::vision_analyze_poll(&job_id)));
8017 },
8018 #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
8019 "vision_analyze_done" => {
8020 let job_id = self.arg_str(&args, 0, "");
8021 return Ok(Value::Bool(vision::vision_analyze_done(&job_id)));
8022 },
8023 #[cfg(all(not(target_arch = "wasm32"), feature = "vision"))]
8025 "vision_analyze_error" => {
8026 let job_id = self.arg_str(&args, 0, "");
8027 return Ok(Value::Str(vision::vision_analyze_error(&job_id)));
8028 },
8029
8030 "url_decode" | "网址解码" => {
8033 let s = self.arg_str(&args, 0, "");
8034 let bytes = s.as_bytes();
8035 let mut out = Vec::with_capacity(bytes.len());
8036 let mut i = 0;
8037 while i < bytes.len() {
8038 match bytes[i] {
8039 b'+' => {
8040 out.push(b' ');
8041 i += 1;
8042 },
8043 b'%' if i + 2 < bytes.len() => {
8044 let hex = std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or("");
8045 match u8::from_str_radix(hex, 16) {
8046 Ok(b) => {
8047 out.push(b);
8048 i += 3;
8049 },
8050 Err(_) => {
8051 out.push(bytes[i]);
8052 i += 1;
8053 },
8054 }
8055 },
8056 b => {
8057 out.push(b);
8058 i += 1;
8059 },
8060 }
8061 }
8062 return Ok(Value::Str(String::from_utf8_lossy(&out).into_owned()));
8063 },
8064 "now_unix" | "现在时间" => {
8068 return Ok(Value::Number(now_secs()));
8069 },
8070 "file_exists" | "文件存在" => {
8071 #[cfg(target_arch = "wasm32")]
8072 return Ok(Value::Bool(false));
8073 #[cfg(not(target_arch = "wasm32"))]
8074 {
8075 let path = self.arg_str(&args, 0, "").replace('\\', "/");
8076 return Ok(Value::Bool(std::path::Path::new(&path).exists()));
8077 }
8078 },
8079 "write_file" | "เขียนไฟล์" | "نوشتن_فایل" | "اكتب_الملف" | "כתוב_קובץ" | "فائل_لکھو" => {
8080 #[cfg(target_arch = "wasm32")]
8081 return Ok(Value::Unit);
8082 #[cfg(not(target_arch = "wasm32"))]
8083 {
8084 let path = self.arg_str(&args, 0, "").replace('\\', "/");
8085 let content = self.arg_str(&args, 1, "");
8086 if let Some(parent) = std::path::Path::new(&path).parent() {
8087 if !parent.as_os_str().is_empty() {
8088 let _ = std::fs::create_dir_all(parent);
8089 }
8090 }
8091 std::fs::write(&path, content.as_bytes())
8092 .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
8093 return Ok(Value::Unit);
8094 }
8095 },
8096 "print_file" | "พิมพ์ไฟล์" | "چاپ_فایل" | "اطبع_الملف" | "הדפס_קובץ" | "فائل_چھاپو" => {
8097 let content = self.arg_str(&args, 0, "");
8098 print!("{content}");
8099 return Ok(Value::Unit);
8100 },
8101
8102 "get_args" | "รับอาร์กิวเมนต์" | "دریافت_آرگومانها" | "اجلب_المعاملات" | "קבל_ארגומנטים" | "دلائل_حاصل_کرو" => {
8104 let v: Vec<Value> = std::env::args().map(Value::Str).collect();
8105 return Ok(Value::List(Rc::new(v)));
8106 },
8107
8108 #[cfg(not(target_arch = "wasm32"))]
8113 "list_dir" | "รายการไดเรกทอรี" | "فهرست_پوشه" | "اسرد_المجلد" | "רשום_תיקייה" | "فولڈر_فہرست" => {
8114 let path = self.arg_str(&args, 0, ".").replace('\\', "/");
8115 let mut paths: Vec<String> = Vec::new();
8116 if let Ok(rd) = std::fs::read_dir(&path) {
8117 for e in rd.flatten() {
8118 paths.push(e.path().to_string_lossy().replace('\\', "/"));
8119 }
8120 }
8121 paths.sort();
8122 let out: Vec<Value> = paths.into_iter().map(Value::Str).collect();
8123 return Ok(Value::List(Rc::new(out)));
8124 },
8125 #[cfg(not(target_arch = "wasm32"))]
8126 "is_dir" | "เป็นไดเรกทอรี" | "آیا_پوشه_است" | "هل_مجلد" | "האם_תיקייה" | "کیا_فولڈر_ہے" => {
8127 let path = self.arg_str(&args, 0, "").replace('\\', "/");
8128 return Ok(Value::Bool(std::path::Path::new(&path).is_dir()));
8129 },
8130 #[cfg(not(target_arch = "wasm32"))]
8131 "is_file" | "เป็นไฟล์" | "آیا_فایل_است" | "هل_ملف" | "האם_קובץ" | "کیا_فائل_ہے" => {
8132 let path = self.arg_str(&args, 0, "").replace('\\', "/");
8133 return Ok(Value::Bool(std::path::Path::new(&path).is_file()));
8134 },
8135 #[cfg(not(target_arch = "wasm32"))]
8136 "path_name" | "ชื่อไฟล์" | "نام_مسیر" | "اسم_المسار" | "שם_נתיב" | "پاتھ_نام" => {
8137 let path = self.arg_str(&args, 0, "").replace('\\', "/");
8138 let name = std::path::Path::new(&path)
8139 .file_name()
8140 .map(|s| s.to_string_lossy().into_owned())
8141 .unwrap_or_default();
8142 return Ok(Value::Str(name));
8143 },
8144 #[cfg(not(target_arch = "wasm32"))]
8145 "path_ext" | "นามสกุลไฟล์" | "پسوند_مسیر" | "امتداد_المسار" | "סיומת_נתיב" | "پاتھ_ایکسٹینشن" => {
8146 let path = self.arg_str(&args, 0, "").replace('\\', "/");
8147 let ext = std::path::Path::new(&path)
8148 .extension()
8149 .map(|s| s.to_string_lossy().to_lowercase())
8150 .unwrap_or_default();
8151 return Ok(Value::Str(ext));
8152 },
8153 #[cfg(not(target_arch = "wasm32"))]
8154 "file_size" | "ขนาดไฟล์" | "اندازه_فایل" | "حجم_الملف" | "גודל_קובץ" | "فائل_سائز" => {
8155 let path = self.arg_str(&args, 0, "");
8156 let sz = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
8157 return Ok(Value::Number(sz as f64));
8158 },
8159 #[cfg(not(target_arch = "wasm32"))]
8160 "file_modified" | "เวลาที่แก้ไข" | "زمان_تغییر_فایل" | "وقت_تعديل_الملف" | "זמן_עדכון_קובץ" | "فائل_ترمیم_وقت" => {
8161 let path = self.arg_str(&args, 0, "");
8162 let secs = std::fs::metadata(&path)
8163 .and_then(|m| m.modified())
8164 .ok()
8165 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8166 .map(|d| d.as_secs_f64())
8167 .unwrap_or(0.0);
8168 return Ok(Value::Number(secs));
8169 },
8170 #[cfg(not(target_arch = "wasm32"))]
8171 "file_created" | "เวลาที่สร้าง" | "زمان_ایجاد_فایل" | "وقت_إنشاء_الملف" | "זמן_יצירת_קובץ" | "فائل_تخلیق_وقت" => {
8172 let path = self.arg_str(&args, 0, "");
8173 let secs = std::fs::metadata(&path)
8174 .ok()
8175 .and_then(|m| m.created().or_else(|_| m.modified()).ok())
8176 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8177 .map(|d| d.as_secs_f64())
8178 .unwrap_or(0.0);
8179 return Ok(Value::Number(secs));
8180 },
8181 #[cfg(not(target_arch = "wasm32"))]
8182 "make_dir" | "สร้างไดเรกทอรี" | "ساخت_پوشه" | "أنشئ_مجلدا" | "צור_תיקייה" | "فولڈر_بناؤ" => {
8183 let path = self.arg_str(&args, 0, "");
8184 return Ok(Value::Bool(std::fs::create_dir_all(&path).is_ok()));
8185 },
8186 "str_strip_prefix" | "ตัดคำนำหน้า" | "حذف_پیشوند" | "أزل_البادئة" | "הסר_קידומת" | "سابقہ_ہٹاؤ" => {
8188 let s = self.arg_str(&args, 0, "");
8189 let prefix = self.arg_str(&args, 1, "");
8190 return Ok(Value::Str(
8191 s.strip_prefix(&prefix).unwrap_or(&s).to_string(),
8192 ));
8193 },
8194 #[cfg(not(target_arch = "wasm32"))]
8197 "file_magic" | "มายาไฟล์" | "امضای_فایل" | "توقيع_الملف" | "חתימת_קובץ" | "فائل_میجک" => {
8198 let path = self.arg_str(&args, 0, "");
8199 let kind = match std::fs::File::open(&path) {
8200 Ok(mut f) => {
8201 use std::io::Read;
8202 let mut buf = [0u8; 4];
8203 let n = f.read(&mut buf).unwrap_or(0);
8204 if n >= 2 && buf[0] == 0x1f && buf[1] == 0x8b {
8205 "gzip"
8206 } else if n >= 4 && &buf[0..2] == b"PK" {
8207 "zip"
8208 } else {
8209 "other"
8210 }
8211 },
8212 Err(_) => "missing",
8213 };
8214 return Ok(Value::Str(kind.to_string()));
8215 },
8216 #[cfg(not(target_arch = "wasm32"))]
8218 "copy_file" | "คัดลอกไฟล์" | "کپی_فایل" | "انسخ_الملف" | "העתק_קובץ" | "فائل_کاپی" => {
8219 let src = self.arg_str(&args, 0, "");
8220 let dst = self.arg_str(&args, 1, "");
8221 if let Some(parent) = std::path::Path::new(&dst).parent() {
8222 let _ = std::fs::create_dir_all(parent);
8223 }
8224 return Ok(Value::Bool(std::fs::copy(&src, &dst).is_ok()));
8225 },
8226 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8230 "tar_gz_list" | "รายการทาร์" | "فهرست_TAR_GZ" | "اسرد_TAR_GZ" | "רשום_TAR_GZ" | "TAR_GZ_فہرست" => {
8231 let path = self.arg_str(&args, 0, "");
8232 let mut names: Vec<String> = Vec::new();
8233 if let Ok(file) = std::fs::File::open(&path) {
8234 let gz = flate2::read::GzDecoder::new(file);
8235 let mut ar = tar::Archive::new(gz);
8236 if let Ok(entries) = ar.entries() {
8237 for entry in entries.flatten() {
8238 if entry.header().entry_type().is_file() {
8239 if let Ok(p) = entry.path() {
8240 names.push(
8241 p.to_string_lossy()
8242 .trim_start_matches("./")
8243 .replace('\\', "/"),
8244 );
8245 }
8246 }
8247 }
8248 }
8249 }
8250 names.sort();
8251 names.dedup();
8252 let out: Vec<Value> = names.into_iter().map(Value::Str).collect();
8253 return Ok(Value::List(Rc::new(out)));
8254 },
8255 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8259 "tar_gz_read" | "อ่านทาร์" | "خواندن_TAR_GZ" | "اقرأ_TAR_GZ" | "קרא_TAR_GZ" | "TAR_GZ_پڑھو" => {
8260 let path = self.arg_str(&args, 0, "");
8261 let want = self.arg_str(&args, 1, "");
8262 let want = want.trim_start_matches("./").replace('\\', "/");
8263 let mut content = String::new();
8264 if let Ok(file) = std::fs::File::open(&path) {
8265 let gz = flate2::read::GzDecoder::new(file);
8266 let mut ar = tar::Archive::new(gz);
8267 if let Ok(entries) = ar.entries() {
8268 for entry in entries.flatten() {
8269 let mut entry = entry;
8270 let name = match entry.path() {
8271 Ok(p) => p
8272 .to_string_lossy()
8273 .trim_start_matches("./")
8274 .replace('\\', "/"),
8275 Err(_) => continue,
8276 };
8277 if name == want {
8278 use std::io::Read;
8279 let mut buf = Vec::new();
8280 let cap = 256 * 1024;
8281 if entry.take(cap as u64 + 1).read_to_end(&mut buf).is_ok() {
8282 let slice = if buf.len() > cap { &buf[..cap] } else { &buf[..] };
8283 content = String::from_utf8_lossy(slice).into_owned();
8284 }
8285 break;
8286 }
8287 }
8288 }
8289 }
8290 return Ok(Value::Str(content));
8291 },
8292 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8299 "totp_secret" | "โทเทนลับ" | "راز_TOTP" | "سر_TOTP" | "סוד_TOTP" | "TOTP_راز" => {
8300 let mut bytes = [0u8; 20];
8301 rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut bytes);
8302 return Ok(Value::Str(base32_encode(&bytes)));
8303 },
8304 "totp_uri" | "โทเทนยูอาร์ไอ" | "آدرس_TOTP" | "رابط_TOTP" | "כתובת_TOTP" | "TOTP_یو_آر_آئی" => {
8308 let secret = self.arg_str(&args, 0, "");
8309 let account = self.arg_str(&args, 1, "user");
8310 let issuer = self.arg_str(&args, 2, "lingfu");
8311 return Ok(Value::Str(format!(
8312 "otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}&algorithm=SHA1&digits=6&period=30"
8313 )));
8314 },
8315 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8317 "totp_verify" | "โทเทนตรวจ" | "تایید_TOTP" | "تحقق_TOTP" | "אמת_TOTP" | "TOTP_تصدیق" => {
8318 let secret = self.arg_str(&args, 0, "");
8319 let code = self.arg_str(&args, 1, "");
8320 let ok = totp_check(&secret, code.trim());
8321 return Ok(Value::Bool(ok));
8322 },
8323 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8325 "totp_now" | "โทเทนตอนนี้" | "TOTP_اکنون" | "TOTP_الآن" | "TOTP_עכשיו" | "TOTP_ابھی" => {
8326 let secret = self.arg_str(&args, 0, "");
8327 let step = (crate::runtime::now_secs() as u64) / 30;
8328 return Ok(Value::Str(
8329 totp_code(&secret, step).unwrap_or_default(),
8330 ));
8331 },
8332 #[cfg(not(target_arch = "wasm32"))]
8334 "file_hash" | "แฮชไฟล์" | "درهم_فایل" | "بصمة_الملف" | "גיבוב_קובץ" | "فائل_ہیش" => {
8335 let path = self.arg_str(&args, 0, "");
8336 match std::fs::read(&path) {
8337 Ok(bytes) => {
8338 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(&bytes))))
8339 },
8340 Err(_) => return Ok(Value::Str(String::new())),
8341 }
8342 },
8343 "hash_hex" | "แฮชสตริง" | "درهم_هگزادسیمال" | "بصمة_سداسية" | "גיבוב_הקסדצימלי" | "ہیکس_ہیش" => {
8345 let s = self.arg_str(&args, 0, "");
8346 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(
8347 s.as_bytes(),
8348 ))));
8349 },
8350 #[cfg(not(target_arch = "wasm32"))]
8352 "env_get" | "รับตัวแปรแวดล้อม" | "دریافت_متغیر_محیطی" | "اجلب_متغير_البيئة" | "קבל_משתנה_סביבה" | "ماحولیاتی_متغیر_حاصل_کرو" => {
8353 let name = self.arg_str(&args, 0, "");
8354 let dflt = self.arg_str(&args, 1, "");
8355 return Ok(Value::Str(std::env::var(&name).unwrap_or(dflt)));
8356 },
8357
8358 "to_number" | "转数字" => {
8362 let s = self.arg_str(&args, 0, "");
8363 return Ok(Value::Number(s.trim().parse().unwrap_or(0.0)));
8364 },
8365 "sha256_hex" | "SHA256哈希" => {
8370 use sha2::Digest;
8371 let s = self.arg_str(&args, 0, "");
8372 let mut h = sha2::Sha256::new();
8373 h.update(s.as_bytes());
8374 return Ok(Value::Str(hex_encode(&h.finalize())));
8375 },
8376 "hex_to_number" | "十六进制转数字" => {
8379 let s = self.arg_str(&args, 0, "");
8380 let v = u64::from_str_radix(s.trim(), 16).unwrap_or(0);
8381 return Ok(Value::Number(v as f64));
8382 },
8383 "split" | "str_split" | "แยก" | "جداسازی" | "قسّم" | "פצל" | "تقسیم_کرو" => {
8384 let s = self.arg_str(&args, 0, "");
8385 let sep = self.arg_str(&args, 1, "\n");
8386 let sep = if sep.is_empty() { "\n".into() } else { sep };
8387 let parts: Vec<Value> = s
8388 .split(sep.as_str())
8389 .map(|p| Value::Str(p.to_string()))
8390 .collect();
8391 return Ok(Value::List(Rc::new(parts)));
8392 },
8393 "trim" | "str_trim" | "ตัดช่องว่าง" | "حذف_فاصله" | "اقتطع_الفراغات" | "חתוך_רווחים" | "خالی_جگہ_کاٹو" => {
8394 let s = self.arg_str(&args, 0, "");
8395 return Ok(Value::Str(s.trim().to_string()));
8396 },
8397 "starts_with" | "str_starts_with" | "เริ่มด้วย" | "شروع_میشود_با" | "يبدأ_بـ" | "מתחיל_ב" | "شروع_ہوتا_ہے" => {
8398 let s = self.arg_str(&args, 0, "");
8399 let prefix = self.arg_str(&args, 1, "");
8400 return Ok(Value::Bool(s.starts_with(prefix.as_str())));
8401 },
8402 "ends_with" | "str_ends_with" | "ลงท้ายด้วย" | "پایان_مییابد_با" | "ينتهي_بـ" | "מסתיים_ב" | "ختم_ہوتا_ہے" => {
8403 let s = self.arg_str(&args, 0, "");
8404 let suffix = self.arg_str(&args, 1, "");
8405 return Ok(Value::Bool(s.ends_with(suffix.as_str())));
8406 },
8407 "str_replace" | "แทนสตริง" | "جایگزینی_رشته" | "استبدل_النص" | "החלף_מחרוזת" | "اسٹرنگ_تبدیل" => {
8408 let s = self.arg_str(&args, 0, "");
8409 let from = self.arg_str(&args, 1, "");
8410 let to = self.arg_str(&args, 2, "");
8411 return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
8412 },
8413 "str_find" | "หาในสตริง" | "جستجوی_رشته" | "ابحث_في_النص" | "חפש_מחרוזת" | "اسٹرنگ_تلاش" => {
8414 let s = self.arg_str(&args, 0, "");
8415 let needle = self.arg_str(&args, 1, "");
8416 let pos = s
8418 .find(needle.as_str())
8419 .map(|byte_i| s[..byte_i].chars().count() as f64)
8420 .unwrap_or(-1.0);
8421 return Ok(Value::Number(pos));
8422 },
8423 "substr" | "str_slice" | "ส่วนสตริง" | "زیررشته" | "جزء_النص" | "תת_מחרוזת" | "ذیلی_اسٹرنگ" => {
8424 let s = self.arg_str(&args, 0, "");
8425 let start = self.arg_num(&args, 1, 0.0)? as usize;
8426 let len = args
8427 .get(2)
8428 .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
8429 .unwrap_or_else(|| s.chars().count().saturating_sub(start));
8430 let chars: Vec<char> = s.chars().collect();
8431 let end = (start + len).min(chars.len());
8432 let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
8433 return Ok(Value::Str(slice));
8434 },
8435 "to_str" | "str" | "num_str" | "แปลงสตริง" | "تبدیل_به_رشته" | "حوّل_لنص" | "המר_למחרוזת" | "اسٹرنگ_میں_بدلو" => {
8436 let v = args.into_iter().next().unwrap_or(Value::Unit);
8437 return Ok(Value::Str(v.to_string()));
8438 },
8439 "str_repeat" | "ทำซ้ำสตริง" | "تکرار_رشته" | "كرّر_النص" | "חזור_על_מחרוזת" | "اسٹرنگ_دہراؤ" => {
8440 let s = self.arg_str(&args, 0, "");
8441 let n = self.arg_num(&args, 1, 1.0)? as usize;
8442 return Ok(Value::Str(s.repeat(n)));
8443 },
8444 "str_upper" => {
8445 let s = self.arg_str(&args, 0, "");
8446 return Ok(Value::Str(s.to_uppercase()));
8447 },
8448 "str_lower" => {
8449 let s = self.arg_str(&args, 0, "");
8450 return Ok(Value::Str(s.to_lowercase()));
8451 },
8452 "str_len" | "len" | "ความยาว" | "长度" | "長さ" | "길이" | "طول_رشته" | "طول_النص" | "אורך_מחרוזת" | "اسٹرنگ_لمبائی" => {
8453 match args.first() {
8454 Some(Value::Str(s)) => return Ok(Value::Number(s.chars().count() as f64)),
8455 Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
8456 _ => return Ok(Value::Number(0.0)),
8457 }
8458 },
8459
8460 "hash_str" | "แฮช" | "درهم_رشته" | "بصمة_نص" | "גיבוב_מחרוזת" | "اسٹرنگ_ہیش" => {
8462 let s = self.arg_str(&args, 0, "");
8463 let mut h: u64 = 14695981039346656037_u64;
8464 for b in s.bytes() {
8465 h ^= b as u64;
8466 h = h.wrapping_mul(1099511628211);
8467 }
8468 return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
8469 },
8470 "hash_int" | "แฮชจำนวน" | "درهم_عدد" | "بصمة_عدد" | "גיבוב_מספר" | "نمبر_ہیش" => {
8471 let s = self.arg_str(&args, 0, "");
8472 let n = self.arg_num(&args, 1, 100.0)? as u64;
8473 let mut h: u64 = 14695981039346656037_u64;
8474 for b in s.bytes() {
8475 h ^= b as u64;
8476 h = h.wrapping_mul(1099511628211);
8477 }
8478 return Ok(Value::Number((h % n.max(1)) as f64));
8479 },
8480
8481 "list_new" | "รายการใหม่" | "新建列表" | "新規リスト" | "새목록" | "فهرست_جدید" | "قائمة_جديدة" | "רשימה_חדשה" | "نئی_فہرست" | "nouvelle_liste" | "neue_liste" | "новый_список" =>
8483 {
8484 return Ok(Value::List(Rc::new(Vec::new())));
8485 },
8486 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가" | "افزودن_به_فهرست" | "أضف_للقائمة" | "הוסף_לרשימה" | "فہرست_میں_شامل_کرو" | "ajouter_liste" | "liste_anhängen" | "добавить_в_список" =>
8487 {
8488 let lst = args
8489 .first()
8490 .cloned()
8491 .unwrap_or(Value::List(Rc::new(vec![])));
8492 let val = args.get(1).cloned().unwrap_or(Value::Unit);
8493 if let Value::List(mut v) = lst {
8494 Rc::make_mut(&mut v).push(val);
8495 return Ok(Value::List(v));
8496 }
8497 return Ok(Value::List(Rc::new(vec![val])));
8498 },
8499 "list_get" | "รับรายการ" | "取元素" | "要素取得" | "요소가져오기" | "دریافت_از_فهرست" | "اجلب_من_القائمة" | "קבל_מרשימה" | "فہرست_سے_حاصل_کرو" | "obtenir_liste" | "liste_abrufen" | "получить_из_списка" =>
8500 {
8501 let i = self.arg_num(&args, 1, 0.0)? as usize;
8503 if let Some(Value::List(v)) = args.first() {
8504 return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
8505 }
8506 return Ok(Value::Str(String::new()));
8507 },
8508 "list_max" | "列表最大值" => {
8512 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8513 let default = self.arg_num(&args, 1, 0.0)?;
8514 if let Value::List(v) = lst {
8515 let mut best = default;
8516 let mut any = false;
8517 for item in v.iter() {
8518 if let Value::Number(n) = item {
8519 if !any || *n > best {
8520 best = *n;
8521 any = true;
8522 }
8523 }
8524 }
8525 return Ok(Value::Number(best));
8526 }
8527 return Ok(Value::Number(default));
8528 },
8529 "list_min" | "列表最小值" => {
8530 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8531 let default = self.arg_num(&args, 1, 0.0)?;
8532 if let Value::List(v) = lst {
8533 let mut best = default;
8534 let mut any = false;
8535 for item in v.iter() {
8536 if let Value::Number(n) = item {
8537 if !any || *n < best {
8538 best = *n;
8539 any = true;
8540 }
8541 }
8542 }
8543 return Ok(Value::Number(best));
8544 }
8545 return Ok(Value::Number(default));
8546 },
8547 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정" | "تنظیم_عنصر_فهرست" | "عيّن_عنصر_القائمة" | "קבע_איבר_רשימה" | "فہرست_سیٹ" =>
8551 {
8552 let idx = self.arg_num(&args, 1, 0.0)? as usize;
8553 let mut ai = args.into_iter();
8554 let lst = ai.next().unwrap_or(Value::List(Rc::new(vec![])));
8555 ai.next(); let val = ai.next().unwrap_or(Value::Unit);
8557 if let Value::List(mut v) = lst {
8558 if idx < v.len() {
8559 Rc::make_mut(&mut v)[idx] = val;
8560 }
8561 return Ok(Value::List(v));
8562 }
8563 return Ok(Value::List(Rc::new(vec![])));
8564 },
8565 "list_join" | "join" | "รวมรายการ" | "连接" | "連結" | "연결" | "پیوستن_فهرست" | "اربط_القائمة" | "חבר_רשימה" | "فہرست_جوڑو" =>
8566 {
8567 let lst = args
8568 .first()
8569 .cloned()
8570 .unwrap_or(Value::List(Rc::new(vec![])));
8571 let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8572 if let Value::List(v) = lst {
8573 return Ok(Value::Str(
8574 v.iter()
8575 .map(|x| x.to_string())
8576 .collect::<Vec<_>>()
8577 .join(&sep),
8578 ));
8579 }
8580 return Ok(Value::Str(String::new()));
8581 },
8582 "list_map" | "映射列表" => {
8591 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8592 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8593 if let Value::List(v) = lst {
8594 let mut out = Vec::with_capacity(v.len());
8595 for item in v.iter() {
8596 out.push(self.call_value(f.clone(), vec![item.clone()])?);
8597 }
8598 return Ok(Value::List(Rc::new(out)));
8599 }
8600 return Ok(Value::List(Rc::new(vec![])));
8601 },
8602 "list_filter" | "过滤列表" => {
8603 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8604 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8605 if let Value::List(v) = lst {
8606 let mut out = Vec::new();
8607 for item in v.iter() {
8608 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8609 out.push(item.clone());
8610 }
8611 }
8612 return Ok(Value::List(Rc::new(out)));
8613 }
8614 return Ok(Value::List(Rc::new(vec![])));
8615 },
8616 "list_find" | "查找列表" => {
8618 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8619 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8620 if let Value::List(v) = lst {
8621 for item in v.iter() {
8622 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8623 return Ok(item.clone());
8624 }
8625 }
8626 }
8627 return Ok(Value::Unit);
8628 },
8629 #[cfg(not(target_arch = "wasm32"))]
8633 "blob_f32" | "blob_i32" => {
8634 let s = self.arg_str(&args, 0, "");
8635 let is_i32 = name == "blob_i32";
8636 match decode_blob(&s) {
8637 Ok(bytes) => {
8638 let mut out = Vec::with_capacity(bytes.len() / 4);
8639 for ch in bytes.chunks_exact(4) {
8640 let arr = [ch[0], ch[1], ch[2], ch[3]];
8641 let n = if is_i32 {
8642 i32::from_le_bytes(arr) as f64
8643 } else {
8644 f32::from_le_bytes(arr) as f64
8645 };
8646 out.push(Value::Number(n));
8647 }
8648 return Ok(Value::List(Rc::new(out)));
8649 },
8650 Err(e) => {
8651 eprintln!("blob decode failed: {e}");
8652 return Ok(Value::List(Rc::new(vec![])));
8653 },
8654 }
8655 },
8656
8657 "svg_begin" | "开始SVG" | "เริ่มSVG" | "شروع_SVG" | "ابدأ_SVG" | "התחל_SVG" | "SVG_شروع" | "commencer_svg" | "svg_beginnen" | "начать_svg" => {
8664 let path = self.arg_str(&args, 0, "output.svg");
8665 let width = self.arg_num(&args, 1, 800.0)?;
8666 let height = self.arg_num(&args, 2, 600.0)?;
8667 *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
8668 return Ok(Value::Unit);
8669 },
8670
8671 "svg_rect" | "SVG矩形" | "SVGสี่เหลี่ยม" | "مستطیل_SVG" | "مستطيل_SVG" | "מלבן_SVG" | "SVG_مستطیل" | "rectangle_svg" | "svg_rechteck" | "прямоугольник_svg" => {
8672 let x = self.arg_num(&args, 0, 0.0)?;
8673 let y = self.arg_num(&args, 1, 0.0)?;
8674 let w = self.arg_num(&args, 2, 10.0)?;
8675 let h = self.arg_num(&args, 3, 10.0)?;
8676 let fill = self.arg_str(&args, 4, "#ffffff");
8677 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8678 svg.elements.push(format!(
8679 "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
8680 height=\"{h:.1}\" fill=\"{fill}\"/>"
8681 ));
8682 }
8683 return Ok(Value::Unit);
8684 },
8685
8686 "svg_circle" | "SVG圆形" | "SVGวงกลม" | "دایره_SVG" | "دائرة_SVG" | "עיגול_SVG" | "SVG_دائرہ" | "cercle_svg" | "svg_kreis" | "круг_svg" => {
8687 let cx = self.arg_num(&args, 0, 0.0)?;
8688 let cy = self.arg_num(&args, 1, 0.0)?;
8689 let r = self.arg_num(&args, 2, 5.0)?;
8690 let fill = self.arg_str(&args, 3, "#ffffff");
8691 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8692 svg.elements.push(format!(
8693 "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"
8694 ));
8695 }
8696 return Ok(Value::Unit);
8697 },
8698
8699 "svg_line" | "SVG线段" | "SVGเส้น" | "خط_SVG" | "קו_SVG" | "SVG_لکیر" | "ligne_svg" | "svg_linie" | "линия_svg" => {
8700 let x1 = self.arg_num(&args, 0, 0.0)?;
8701 let y1 = self.arg_num(&args, 1, 0.0)?;
8702 let x2 = self.arg_num(&args, 2, 0.0)?;
8703 let y2 = self.arg_num(&args, 3, 0.0)?;
8704 let stroke = self.arg_str(&args, 4, "#ffffff");
8705 let sw = self.arg_num(&args, 5, 1.0)?;
8706 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8707 svg.elements.push(format!(
8708 "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
8709 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8710 ));
8711 }
8712 return Ok(Value::Unit);
8713 },
8714
8715 "svg_polyline" | "SVG折线" | "SVGเส้นหัก" | "چندخطی_SVG" | "خط_متعدد_SVG" | "קו_שבור_SVG" | "SVG_پولی_لائن" | "polyligne_svg" | "svg_polylinie" | "ломаная_svg" => {
8716 let pts = self.arg_str(&args, 0, "");
8717 let stroke = self.arg_str(&args, 1, "#ffffff");
8718 let sw = self.arg_num(&args, 2, 1.0)?;
8719 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8720 svg.elements.push(format!(
8721 "<polyline points=\"{pts}\" fill=\"none\" \
8722 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8723 ));
8724 }
8725 return Ok(Value::Unit);
8726 },
8727
8728 "svg_text" | "SVG文本" | "SVGข้อความ" | "متن_SVG" | "نص_SVG" | "טקסט_SVG" | "SVG_متن" | "texte_svg" | "текст_svg" => {
8729 let x = self.arg_num(&args, 0, 0.0)?;
8730 let y = self.arg_num(&args, 1, 0.0)?;
8731 let text = self.arg_str(&args, 2, "");
8732 let fill = self.arg_str(&args, 3, "#ffffff");
8733 let size = self.arg_num(&args, 4, 12.0)?;
8734 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8735 let safe = text
8736 .replace('&', "&")
8737 .replace('<', "<")
8738 .replace('>', ">");
8739 svg.elements.push(format!(
8740 "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
8741 font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"
8742 ));
8743 }
8744 return Ok(Value::Unit);
8745 },
8746
8747 "svg_end" | "结束SVG" | "จบSVG" | "پایان_SVG" | "أنهِ_SVG" | "סיים_SVG" | "SVG_ختم" | "terminer_svg" | "svg_beenden" | "закончить_svg" => {
8748 {
8749 let borrow = self.svg.borrow();
8750 if let Some(svg) = borrow.as_ref() {
8751 svg.save()
8752 .map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
8753 }
8754 }
8755 *self.svg.borrow_mut() = None;
8756 return Ok(Value::Unit);
8757 },
8758
8759 "hsl_color" | "HSL颜色" | "สีHSL" | "رنگ_HSL" | "لون_HSL" | "צבע_HSL" | "HSL_رنگ" | "couleur_hsl" | "hsl_farbe" | "цвет_hsl" => {
8760 let h = self.arg_num(&args, 0, 0.0)?;
8761 let s = self.arg_num(&args, 1, 70.0)?;
8762 let l = self.arg_num(&args, 2, 50.0)?;
8763 return Ok(Value::Str(hsl_to_hex(h, s, l)));
8764 },
8765
8766 #[cfg(not(target_arch = "wasm32"))]
8772 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" | "ورودی_FFT" | "أدخل_FFT" | "הזנת_FFT" | "FFT_ان_پٹ" | "fft_entrée" | "fft_eingabe" | "fft_вход" =>
8773 {
8774 if let Some(Value::List(v)) = args.first() {
8775 let samples: Vec<f32> = v
8776 .iter()
8777 .filter_map(|x| {
8778 if let Value::Number(n) = x {
8779 Some(*n as f32)
8780 } else {
8781 None
8782 }
8783 })
8784 .collect();
8785 self.fft.borrow_mut().push_samples(&samples);
8786 }
8787 return Ok(Value::Unit);
8788 },
8789
8790 #[cfg(not(target_arch = "wasm32"))]
8792 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" | "باندهای_FFT" | "نطاقات_FFT" | "פסי_FFT" | "FFT_بینڈز" | "fft_bandes" | "fft_bänder" | "fft_полосы" =>
8793 {
8794 let n = self.arg_num(&args, 0, 32.0)? as usize;
8795 let bands = self.fft.borrow().freq_bands(n);
8796 *self.fft_bands_cache.borrow_mut() = bands.clone();
8797 return Ok(Value::List(Rc::new(
8798 bands.into_iter().map(|v| Value::Number(v as f64)).collect(),
8799 )));
8800 },
8801
8802 #[cfg(not(target_arch = "wasm32"))]
8804 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" | "ضرب_FFT" | "نبضة_FFT" | "פעימת_FFT" | "FFT_دھڑکن" | "fft_battement" | "fft_takt" | "fft_удар" =>
8805 {
8806 return Ok(Value::Bool(self.fft.borrow().is_beat()));
8807 },
8808
8809 #[cfg(not(target_arch = "wasm32"))]
8811 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" | "نسبت_ضرب_FFT" | "نسبة_نبضة_FFT" | "יחס_פעימת_FFT" | "FFT_بیٹ_تناسب" =>
8812 {
8813 return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
8814 },
8815
8816 #[cfg(not(target_arch = "wasm32"))]
8818 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" | "RMS_صدا" | "جذر_متوسط_مربع_FFT" | "RMS_של_FFT" | "FFT_RMS" => {
8819 return Ok(Value::Number(self.fft.borrow().rms() as f64));
8820 },
8821
8822 #[cfg(not(target_arch = "wasm32"))]
8824 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" | "فرکانس_غالب" | "التردد_السائد" | "תדר_דומיננטי" | "غالب_فریکوئنسی" | "fft_fréquence_dominante" | "fft_dominante_frequenz" | "fft_доминирующая_частота" =>
8825 {
8826 return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
8827 },
8828
8829 #[cfg(target_arch = "wasm32")]
8831 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" | "ورودی_FFT" | "أدخل_FFT" | "הזנת_FFT" | "FFT_ان_پٹ" | "fft_entrée" | "fft_eingabe" | "fft_вход" =>
8832 {
8833 return Ok(Value::Unit);
8834 },
8835 #[cfg(target_arch = "wasm32")]
8836 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" | "باندهای_FFT" | "نطاقات_FFT" | "פסי_FFT" | "FFT_بینڈز" | "fft_bandes" | "fft_bänder" | "fft_полосы" =>
8837 {
8838 let n = self.arg_num(&args, 0, 32.0)? as usize;
8839 return Ok(Value::List(vec![Value::Number(0.0); n].into()));
8840 },
8841 #[cfg(target_arch = "wasm32")]
8842 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" | "ضرب_FFT" | "نبضة_FFT" | "פעימת_FFT" | "FFT_دھڑکن" | "fft_battement" | "fft_takt" | "fft_удар" =>
8843 {
8844 return Ok(Value::Bool(false));
8845 },
8846 #[cfg(target_arch = "wasm32")]
8847 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" | "نسبت_ضرب_FFT" | "نسبة_نبضة_FFT" | "יחס_פעימת_FFT" | "FFT_بیٹ_تناسب" =>
8848 {
8849 return Ok(Value::Number(1.0));
8850 },
8851 #[cfg(target_arch = "wasm32")]
8852 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" | "RMS_صدا" | "جذر_متوسط_مربع_FFT" | "RMS_של_FFT" | "FFT_RMS" => {
8853 return Ok(Value::Number(0.0));
8854 },
8855 #[cfg(target_arch = "wasm32")]
8856 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" | "فرکانس_غالب" | "التردد_السائد" | "תדר_דומיננטי" | "غالب_فریکوئنسی" | "fft_fréquence_dominante" | "fft_dominante_frequenz" | "fft_доминирующая_частота" =>
8857 {
8858 return Ok(Value::Number(0.0));
8859 },
8860
8861 "tex_checkerboard" | "ลายตารางหมากรุก" | "بافت_شطرنجی" | "نسيج_رقعة_الشطرنج" | "מרקם_שחמט" | "شطرنج_ٹیکسچر" => {
8869 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8870 let tiles = self.arg_num(&args, 4, 8.0)? as u32;
8871 let (r1, g1, b1) = (
8872 self.arg_num(&args, 5, 255.)? as u32,
8873 self.arg_num(&args, 6, 255.)? as u32,
8874 self.arg_num(&args, 7, 255.)? as u32,
8875 );
8876 let (r2, g2, b2) = (
8877 self.arg_num(&args, 8, 0.)? as u32,
8878 self.arg_num(&args, 9, 0.)? as u32,
8879 self.arg_num(&args, 10, 0.)? as u32,
8880 );
8881 let c1 = (r1 << 16) | (g1 << 8) | b1;
8882 let c2 = (r2 << 16) | (g2 << 8) | b2;
8883 let mut gfx = self.gfx.borrow_mut();
8884 let (bw, bh) = (gfx.width, gfx.height);
8885 for row in 0..th {
8886 for col in 0..tw {
8887 let cx = col as u32 * tiles / tw as u32;
8888 let cy = row as u32 * tiles / th as u32;
8889 let (dx, dy) = (tx + col, ty + row);
8890 if dx < bw && dy < bh {
8891 gfx.buffer[dy * bw + dx] = if (cx + cy) % 2 == 0 { c1 } else { c2 };
8892 }
8893 }
8894 }
8895 return Ok(Value::Unit);
8896 },
8897
8898 "tex_gradient" | "ลายไล่สี" | "بافت_گرادیان" | "نسيج_متدرج" | "מרקם_גרדיאנט" | "گریڈینٹ_ٹیکسچر" => {
8900 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8901 let angle = self.arg_num(&args, 4, 0.0)? as f32;
8902 let (r1, g1, b1) = (
8903 self.arg_num(&args, 5, 0.)? as f32 / 255.,
8904 self.arg_num(&args, 6, 0.)? as f32 / 255.,
8905 self.arg_num(&args, 7, 0.)? as f32 / 255.,
8906 );
8907 let (r2, g2, b2) = (
8908 self.arg_num(&args, 8, 255.)? as f32 / 255.,
8909 self.arg_num(&args, 9, 255.)? as f32 / 255.,
8910 self.arg_num(&args, 10, 255.)? as f32 / 255.,
8911 );
8912 let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
8913 let mut gfx = self.gfx.borrow_mut();
8914 let (bw, bh) = (gfx.width, gfx.height);
8915 for row in 0..th {
8916 for col in 0..tw {
8917 let nx = col as f32 / tw as f32 - 0.5;
8918 let ny = row as f32 / th as f32 - 0.5;
8919 let t = ((nx * ca + ny * sa + 0.707) / 1.414).clamp(0., 1.);
8920 let (dx, dy) = (tx + col, ty + row);
8921 if dx < bw && dy < bh {
8922 gfx.buffer[dy * bw + dx] =
8923 tex_rgb(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
8924 }
8925 }
8926 }
8927 return Ok(Value::Unit);
8928 },
8929
8930 "tex_noise" | "ลายนอยส์" | "بافت_نویز" | "نسيج_ضجيج" | "מרקם_רעש" | "نوائز_ٹیکسچر" => {
8932 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8933 let scale = self.arg_num(&args, 4, 4.0)? as f32;
8934 let octaves = self.arg_num(&args, 5, 4.0)? as u32;
8935 let seed = self.arg_num(&args, 6, 0.0)? as u32;
8936 let palette = self.arg_str(&args, 7, "rainbow");
8937 let mut gfx = self.gfx.borrow_mut();
8938 let (bw, bh) = (gfx.width, gfx.height);
8939 for row in 0..th {
8940 for col in 0..tw {
8941 let v = tex_fbm(
8942 col as f32 * scale / tw as f32,
8943 row as f32 * scale / th as f32,
8944 octaves,
8945 seed,
8946 );
8947 let [r, g, b] = tex_palette(&palette, v);
8948 let (dx, dy) = (tx + col, ty + row);
8949 if dx < bw && dy < bh {
8950 gfx.buffer[dy * bw + dx] = tex_rgb(r, g, b);
8951 }
8952 }
8953 }
8954 return Ok(Value::Unit);
8955 },
8956
8957 "tex_freq_map" | "ลายความถี่" | "نقشه_فرکانس_بافت" | "خريطة_تردد_النسيج" | "מפת_תדר_מרקם" | "فریکوئنسی_میپ_ٹیکسچر" => {
8960 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8961 let time = self.arg_num(&args, 4, 0.0)? as f32;
8962 let speed = self.arg_num(&args, 5, 0.3)? as f32;
8963 let palette = self.arg_str(&args, 6, "rainbow");
8964 let bands: Vec<f32> = {
8965 let c = self.fft_bands_cache.borrow();
8966 if c.is_empty() {
8967 vec![0.0; 32]
8968 } else {
8969 c.clone()
8970 }
8971 };
8972 let n = bands.len().max(1);
8973 let mut gfx = self.gfx.borrow_mut();
8974 let (bw, bh) = (gfx.width, gfx.height);
8975 for row in 0..th {
8976 for col in 0..tw {
8977 let band_idx = (col * n / tw.max(1)).min(n - 1);
8978 let mag = bands[band_idx].clamp(0., 1.);
8979 let fill_y = (mag * th as f32) as usize;
8980 if row >= th.saturating_sub(fill_y) {
8981 let t = (col as f32 / tw as f32 + time * speed) % 1.0;
8982 let [r, g, b] = tex_palette(&palette, t);
8983 let bright = mag * (1.0 - row as f32 / th as f32 * 0.5);
8984 let (dx, dy) = (tx + col, ty + row);
8985 if dx < bw && dy < bh {
8986 gfx.buffer[dy * bw + dx] =
8987 tex_rgb(r * bright, g * bright, b * bright);
8988 }
8989 }
8990 }
8991 }
8992 return Ok(Value::Unit);
8993 },
8994
8995 "tex_spiral" | "ลายเกลียวหมุน" | "بافت_مارپیچ" | "نسيج_حلزوني" | "מרקם_ספירלה" | "سرپیچ_ٹیکسچر" => {
8997 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8998 let freq = self.arg_num(&args, 4, 5.0)? as f32;
8999 let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
9000 let time = self.arg_num(&args, 6, 0.0)? as f32;
9001 let palette = self.arg_str(&args, 7, "rainbow");
9002 let mut gfx = self.gfx.borrow_mut();
9003 let (bw, bh) = (gfx.width, gfx.height);
9004 for row in 0..th {
9005 for col in 0..tw {
9006 let nx = col as f32 / tw as f32 - 0.5;
9007 let ny = row as f32 / th as f32 - 0.5;
9008 let r = (nx * nx + ny * ny).sqrt();
9009 let theta = ny.atan2(nx);
9010 let t = ((r * freq - theta / std::f32::consts::TAU + time * 0.5) * n_bands
9011 % 1.0)
9012 .abs();
9013 let [cr, cg, cb] = tex_palette(&palette, t);
9014 let (dx, dy) = (tx + col, ty + row);
9015 if dx < bw && dy < bh {
9016 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9017 }
9018 }
9019 }
9020 return Ok(Value::Unit);
9021 },
9022
9023 "tex_ripple" | "ลายระลอก" | "بافت_موج" | "نسيج_تموج" | "מרקם_אדווה" | "ریپل_ٹیکسچر" => {
9025 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9026 let freq = self.arg_num(&args, 4, 10.0)? as f32;
9027 let rcx = self.arg_num(&args, 5, 0.5)? as f32;
9028 let rcy = self.arg_num(&args, 6, 0.5)? as f32;
9029 let time = self.arg_num(&args, 7, 0.0)? as f32;
9030 let palette = self.arg_str(&args, 8, "ocean");
9031 let mut gfx = self.gfx.borrow_mut();
9032 let (bw, bh) = (gfx.width, gfx.height);
9033 for row in 0..th {
9034 for col in 0..tw {
9035 let nx = col as f32 / tw as f32 - rcx;
9036 let ny = row as f32 / th as f32 - rcy;
9037 let r = (nx * nx + ny * ny).sqrt();
9038 let t = ((r * freq - time) % 1.0).abs();
9039 let [cr, cg, cb] = tex_palette(&palette, t);
9040 let (dx, dy) = (tx + col, ty + row);
9041 if dx < bw && dy < bh {
9042 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9043 }
9044 }
9045 }
9046 return Ok(Value::Unit);
9047 },
9048
9049 "tex_mandelbrot" | "ลายแมนเดลบรอต" | "بافت_ماندلبرو" | "نسيج_مانديلبروت" | "מרקם_מנדלברוט" | "مینڈل_بروٹ_ٹیکسچر" => {
9051 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9052 let zoom = self.arg_num(&args, 4, 1.0)?;
9053 let mcx = self.arg_num(&args, 5, -0.5)?;
9054 let mcy = self.arg_num(&args, 6, 0.0)?;
9055 let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
9056 let palette = self.arg_str(&args, 8, "psychedelic");
9057 let mut gfx = self.gfx.borrow_mut();
9058 let (bw, bh) = (gfx.width, gfx.height);
9059 for row in 0..th {
9060 for col in 0..tw {
9061 let zx0 = (col as f64 / tw as f64 - 0.5) / zoom + mcx;
9062 let zy0 = (row as f64 / th as f64 - 0.5) / zoom + mcy;
9063 let mut x = 0.0f64;
9064 let mut y = 0.0f64;
9065 let mut i = 0u32;
9066 while i < max_iter && x * x + y * y < 4.0 {
9067 let t = x * x - y * y + zx0;
9068 y = 2.0 * x * y + zy0;
9069 x = t;
9070 i += 1;
9071 }
9072 let t = if i == max_iter {
9073 0.0f32
9074 } else {
9075 (i as f32
9076 - (x as f32 * x as f32 + y as f32 * y as f32).ln().ln()
9077 / 2.0f32.ln())
9078 / max_iter as f32
9079 };
9080 let [cr, cg, cb] = tex_palette(&palette, t.clamp(0., 1.));
9081 let (dx, dy) = (tx + col, ty + row);
9082 if dx < bw && dy < bh {
9083 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9084 }
9085 }
9086 }
9087 return Ok(Value::Unit);
9088 },
9089
9090 "tex_julia" | "ลายจูเลีย" | "بافت_ژولیا" | "نسيج_جوليا" | "מרקם_ג'וליה" | "جولیا_ٹیکسچر" => {
9092 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9093 let c_re = self.arg_num(&args, 4, -0.7)?;
9094 let c_im = self.arg_num(&args, 5, 0.27)?;
9095 let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
9096 let palette = self.arg_str(&args, 7, "neon");
9097 let mut gfx = self.gfx.borrow_mut();
9098 let (bw, bh) = (gfx.width, gfx.height);
9099 for row in 0..th {
9100 for col in 0..tw {
9101 let mut zx = (col as f64 / tw as f64 - 0.5) * 3.5;
9102 let mut zy = (row as f64 / th as f64 - 0.5) * 3.5;
9103 let mut i = 0u32;
9104 while i < max_iter && zx * zx + zy * zy < 4.0 {
9105 let t = zx * zx - zy * zy + c_re;
9106 zy = 2.0 * zx * zy + c_im;
9107 zx = t;
9108 i += 1;
9109 }
9110 let t = i as f32 / max_iter as f32;
9111 let [cr, cg, cb] = tex_palette(&palette, t);
9112 let (dx, dy) = (tx + col, ty + row);
9113 if dx < bw && dy < bh {
9114 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9115 }
9116 }
9117 }
9118 return Ok(Value::Unit);
9119 },
9120
9121 "tex_voronoi" | "ลายโวโรนอย" | "بافت_ورونوی" | "نسيج_فورونوي" | "מרקם_וורונוי" | "ورونوئی_ٹیکسچر" => {
9123 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9124 let cells = self.arg_num(&args, 4, 16.0)? as u32;
9125 let seed = self.arg_num(&args, 5, 42.0)? as u32;
9126 let palette = self.arg_str(&args, 6, "rainbow");
9127 let pts: Vec<[f32; 2]> = (0..cells)
9128 .map(|i| {
9129 [
9130 tex_hash(i as i32, 0, seed),
9131 tex_hash(i as i32, 1, seed + 999),
9132 ]
9133 })
9134 .collect();
9135 let mut gfx = self.gfx.borrow_mut();
9136 let (bw, bh) = (gfx.width, gfx.height);
9137 for row in 0..th {
9138 for col in 0..tw {
9139 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
9140 let (min_d, nearest) = pts.iter().enumerate().fold(
9141 (f32::MAX, 0usize),
9142 |(d, idx), (i, &[cx, cy])| {
9143 let dd = (fx - cx).powi(2) + (fy - cy).powi(2);
9144 if dd < d {
9145 (dd, i)
9146 } else {
9147 (d, idx)
9148 }
9149 },
9150 );
9151 let t = (nearest as f32 / cells as f32 + min_d * 4.0) % 1.0;
9152 let [cr, cg, cb] = tex_palette(&palette, t);
9153 let (dx, dy) = (tx + col, ty + row);
9154 if dx < bw && dy < bh {
9155 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9156 }
9157 }
9158 }
9159 return Ok(Value::Unit);
9160 },
9161
9162 "tex_halftone" | "ลายฮาล์ฟโทน" | "بافت_نیمتن" | "نسيج_نصفي" | "מרקם_חצי_גוון" | "ہاف_ٹون_ٹیکسچر" => {
9164 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9165 let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
9166 let time = self.arg_num(&args, 5, 0.0)? as f32;
9167 let palette = self.arg_str(&args, 6, "rainbow");
9168 let mut gfx = self.gfx.borrow_mut();
9169 let (bw, bh) = (gfx.width, gfx.height);
9170 for row in 0..th {
9171 for col in 0..tw {
9172 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
9173 let gx = (fx / dot_size).floor();
9174 let gy = (fy / dot_size).floor();
9175 let lx = (fx / dot_size - gx - 0.5) * 2.0;
9176 let ly = (fy / dot_size - gy - 0.5) * 2.0;
9177 let r = (lx * lx + ly * ly).sqrt();
9178 let t = (gx / (1.0 / dot_size) + time * 0.1) % 1.0;
9179 let a = if r < 0.7 {
9180 ((0.7 - r) / 0.7).clamp(0., 1.)
9181 } else {
9182 0.0
9183 };
9184 if a > 0.0 {
9185 let [cr, cg, cb] = tex_palette(&palette, t);
9186 let (dx, dy) = (tx + col, ty + row);
9187 if dx < bw && dy < bh {
9188 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9189 }
9190 }
9191 }
9192 }
9193 return Ok(Value::Unit);
9194 },
9195
9196 "set_shade_mode" | "设置着色" | "シェード設定" | "셰이드모드" | "ตั้งการแรเงา" | "تنظیم_حالت_سایهپردازی" | "عيّن_نمط_التظليل" | "קבע_מצב_הצללה" | "شیڈ_موڈ_مقرر_کرو" | "définir_mode_ombrage" | "schattierungsmodus_setzen" | "задать_режим_затенения" =>
9201 {
9202 let m = self.arg_num(&args, 0, 2.0)? as u8;
9203 self.gfx.borrow_mut().shade_mode = m;
9204 return Ok(Value::Unit);
9205 },
9206 "set_cel_bands" | "设置色阶" | "セル段数" | "셀밴드" | "ตั้งระดับสี" | "تنظیم_باندهای_سل" | "عيّن_نطاقات_التظليل" | "קבע_רצועות_הצללה" | "سیل_بینڈز_مقرر_کرو" | "définir_bandes_cel" | "cel_bänder_setzen" | "задать_полосы_cel" =>
9208 {
9209 let n = (self.arg_num(&args, 0, 4.0)? as u32).max(2);
9210 self.gfx.borrow_mut().shade.bands = n;
9211 return Ok(Value::Unit);
9212 },
9213 "set_shadow_color" | "设置阴影色" | "影の色" | "그림자색" | "ตั้งสีเงา" | "تنظیم_رنگ_سایه" | "عيّن_لون_الظل" | "קבע_צבע_צל" | "سایہ_رنگ_مقرر_کرو" | "définir_couleur_ombre" | "schattenfarbe_setzen" | "задать_цвет_тени" =>
9215 {
9216 let r = self.arg_num(&args, 0, 26.)? as f32 / 255.0;
9217 let g = self.arg_num(&args, 1, 33.)? as f32 / 255.0;
9218 let b = self.arg_num(&args, 2, 77.)? as f32 / 255.0;
9219 self.gfx.borrow_mut().shade.shadow = [r, g, b];
9220 return Ok(Value::Unit);
9221 },
9222 #[cfg(not(target_arch = "wasm32"))]
9228 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" | "درهم_رمزنگاری" | "بصمة_تشفير" | "גיבוב_הצפנה" | "خفیہ_ہیش" | "hachage_crypto" | "krypto_hash" | "крипто_хеш" =>
9229 {
9230 let s = self.arg_str(&args, 0, "");
9231 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9232 s.as_bytes(),
9233 ))));
9234 },
9235 #[cfg(target_arch = "wasm32")]
9236 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" | "درهم_رمزنگاری" | "بصمة_تشفير" | "גיבוב_הצפנה" | "خفیہ_ہیش" | "hachage_crypto" | "krypto_hash" | "крипто_хеш" =>
9237 {
9238 let s = self.arg_str(&args, 0, "");
9239 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9240 s.as_bytes(),
9241 ))));
9242 },
9243 #[cfg(not(target_arch = "wasm32"))]
9245 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" | "نقاط_گره" | "نقاط_العقدة" | "נקודות_קשר" | "گرہ_پوائنٹس" | "points_nœud" | "knotenpunkte" | "точки_узла" => {
9246 let s = self.arg_str(&args, 0, "");
9247 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9248 let mut out = Vec::with_capacity(shape.points.len() * 3);
9249 for p in &shape.points {
9250 out.push(Value::Number(p[0] as f64));
9251 out.push(Value::Number(p[1] as f64));
9252 out.push(Value::Number(p[2] as f64));
9253 }
9254 return Ok(Value::List(Rc::new(out)));
9255 },
9256 #[cfg(target_arch = "wasm32")]
9257 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" | "نقاط_گره" | "نقاط_العقدة" | "נקודות_קשר" | "گرہ_پوائنٹس" | "points_nœud" | "knotenpunkte" | "точки_узла" => {
9258 let s = self.arg_str(&args, 0, "");
9259 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9260 let mut out = Vec::with_capacity(shape.points.len() * 3);
9261 for p in &shape.points {
9262 out.push(Value::Number(p[0] as f64));
9263 out.push(Value::Number(p[1] as f64));
9264 out.push(Value::Number(p[2] as f64));
9265 }
9266 return Ok(Value::List(out.into()));
9267 },
9268 #[cfg(not(target_arch = "wasm32"))]
9269 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" | "برچسب_گره" | "تسمية_العقدة" | "תווית_קשר" | "گرہ_لیبل" | "étiquette_nœud" | "knotenbezeichnung" | "метка_узла" =>
9270 {
9271 let s = self.arg_str(&args, 0, "");
9272 return Ok(Value::Str(
9273 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9274 ));
9275 },
9276 #[cfg(target_arch = "wasm32")]
9277 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" | "برچسب_گره" | "تسمية_العقدة" | "תווית_קשר" | "گرہ_لیبل" | "étiquette_nœud" | "knotenbezeichnung" | "метка_узла" =>
9278 {
9279 let s = self.arg_str(&args, 0, "");
9280 return Ok(Value::Str(
9281 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9282 ));
9283 },
9284 #[cfg(not(target_arch = "wasm32"))]
9286 "knot_keygen" | "hybrid_keygen" | "สร้างกุญแจปม" | "生成密钥" | "鍵生成" | "키생성" | "تولید_کلید_گره" | "توليد_مفتاح_العقدة" | "יצירת_מפתח_קשר" | "گرہ_کلید_تخلیق" | "génération_clé_nœud" | "knotenschlüsselerzeugung" | "генерация_ключа_узла" =>
9287 {
9288 self.crypto_ids.push(ling_crypto::KnotIdentity::generate());
9289 return Ok(Value::Number((self.crypto_ids.len() - 1) as f64));
9290 },
9291 #[cfg(not(target_arch = "wasm32"))]
9292 "knot_public" | "hybrid_public" | "กุญแจสาธารณะปม" | "公钥" | "公開鍵" | "공개키" | "کلید_عمومی_گره" | "مفتاح_العقدة_العام" | "מפתח_ציבורי_קשר" | "گرہ_عوامی_کلید" | "clé_publique_nœud" | "knoten_öffentlicher_schlüssel" | "публичный_ключ_узла" =>
9293 {
9294 let h = self.arg_num(&args, 0, 0.0)? as usize;
9295 let pk = self
9296 .crypto_ids
9297 .get(h)
9298 .map(|id| hex_encode(id.public_key()))
9299 .unwrap_or_default();
9300 return Ok(Value::Str(pk));
9301 },
9302 #[cfg(not(target_arch = "wasm32"))]
9304 "knot_encapsulate"
9305 | "hybrid_encapsulate"
9306 | "ห่อกุญแจปม"
9307 | "封装密钥"
9308 | "カプセル化"
9309 | "캡슐화" | "کپسولهسازی_گره" | "تغليف_مفتاح_العقدة" | "עטיפת_קשר" | "گرہ_احاطہ" | "encapsuler_nœud" | "knoten_kapseln" | "инкапсулировать_узел" => {
9310 let pk = hex_decode(&self.arg_str(&args, 0, ""));
9311 match ling_crypto::geo::knot_encapsulate(&pk) {
9312 Ok((ct, ss)) => {
9313 return Ok(Value::List(Rc::new(vec![
9314 Value::Str(hex_encode(&ct)),
9315 Value::Str(hex_encode(&ss)),
9316 ])))
9317 },
9318 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9319 }
9320 },
9321 #[cfg(not(target_arch = "wasm32"))]
9323 "knot_decapsulate"
9324 | "hybrid_decapsulate"
9325 | "แกะกุญแจปม"
9326 | "解封装密钥"
9327 | "カプセル解除"
9328 | "캡슐해제" | "بازکردن_کپسوله_گره" | "فك_تغليف_مفتاح_العقدة" | "פתיחת_עטיפת_קשר" | "گرہ_احاطہ_کھولو" | "décapsuler_nœud" | "knoten_entkapseln" | "декапсулировать_узел" => {
9329 let h = self.arg_num(&args, 0, 0.0)? as usize;
9330 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9331 let ss = self
9332 .crypto_ids
9333 .get(h)
9334 .and_then(|id| id.decapsulate(&ct).ok())
9335 .map(|s| hex_encode(&s))
9336 .unwrap_or_default();
9337 return Ok(Value::Str(ss));
9338 },
9339 #[cfg(not(target_arch = "wasm32"))]
9341 "crypto_seal" | "ผนึก" | "封印" | "封印する" | "봉인" | "مهر_رمزنگاری" | "ختم_تشفير" | "חתימת_הצפנה" | "خفیہ_مہر" | "sceller_crypto" | "krypto_versiegeln" | "запечатать_крипто" => {
9342 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9343 let pt = self.arg_str(&args, 1, "");
9344 match ling_crypto::geo::holo_seal(key, pt.as_bytes()) {
9345 Ok(ct) => return Ok(Value::Str(hex_encode(&ct))),
9346 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9347 }
9348 },
9349 #[cfg(not(target_arch = "wasm32"))]
9350 "crypto_open" | "เปิดผนึก" | "解封" | "封印解除" | "봉인해제" | "بازکردن_مهر" | "فتح_الختم" | "פתיחת_חתימה" | "مہر_کھولو" | "ouvrir_crypto" | "krypto_öffnen" | "открыть_крипто" =>
9351 {
9352 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9353 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9354 match ling_crypto::geo::holo_open(key, &ct) {
9355 Ok(pt) => return Ok(Value::Str(String::from_utf8_lossy(&pt).into_owned())),
9356 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9357 }
9358 },
9359 #[cfg(not(target_arch = "wasm32"))]
9361 "holo_points" | "จุดโฮโลแกรม" | "全息点" | "ホログラム点" | "홀로그램점" | "نقاط_هولوگرام" | "نقاط_الهولوغرام" | "נקודות_הולוגרמה" | "ہولوگرام_پوائنٹس" | "points_holo" | "holo_punkte" | "точки_голо" =>
9362 {
9363 let s = self.arg_str(&args, 0, "");
9364 let frags = ling_crypto::geo::scatter(s.as_bytes());
9365 let mut out = Vec::with_capacity(frags.len() * 4);
9366 for f in &frags {
9367 for c in f.coord {
9368 out.push(Value::Number(c as f64));
9369 }
9370 }
9371 return Ok(Value::List(Rc::new(out)));
9372 },
9373 #[cfg(not(target_arch = "wasm32"))]
9374 "holo_fragment_count"
9375 | "จำนวนชิ้นโฮโลแกรม"
9376 | "全息碎片数"
9377 | "ホログラム断片数"
9378 | "홀로그램조각수" | "تعداد_قطعات_هولوگرام" | "عدد_شظايا_الهولوغرام" | "מספר_שברי_הולוגרמה" | "ہولوگرام_ٹکڑے_تعداد" | "nombre_fragments_holo" | "holo_fragmentanzahl" | "число_фрагментов_голо" => {
9379 let s = self.arg_str(&args, 0, "");
9380 return Ok(Value::Number(
9381 ling_crypto::geo::scatter(s.as_bytes()).len() as f64
9382 ));
9383 },
9384 #[cfg(not(target_arch = "wasm32"))]
9387 "shake_hex" | "SHAKE哈希" => {
9388 let s = self.arg_str(&args, 0, "");
9389 let len = self.arg_num(&args, 1, 32.0)?.max(0.0) as usize;
9390 return Ok(Value::Str(hex_encode(&ling_crypto::Shake256::hash(s.as_bytes(), len))));
9391 },
9392 #[cfg(not(target_arch = "wasm32"))]
9394 "ed25519_keygen"
9395 | "생성서명키"
9396 | "สร้างกุญแจลายเซ็น"
9397 | "生成签名密钥"
9398 | "署名鍵生成"
9399 | "تولید_کلید_امضا"
9400 | "توليد_مفتاح_التوقيع"
9401 | "יצירת_מפתח_חתימה"
9402 | "دستخط_کلید_تخلیق"
9403 | "génération_clé_signature"
9404 | "signaturschlüsselerzeugung"
9405 | "генерация_ключа_подписи" => {
9406 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::generate());
9407 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9408 },
9409 #[cfg(not(target_arch = "wasm32"))]
9414 "ed25519_keygen_from_seed"
9415 | "씨앗에서생성서명키"
9416 | "สร้างกุญแจลายเซ็นจากเมล็ด"
9417 | "从种子生成签名密钥"
9418 | "シードから署名鍵生成"
9419 | "تولید_کلید_امضا_از_دانه"
9420 | "توليد_مفتاح_التوقيع_من_البذرة"
9421 | "יצירת_מפתח_חתימה_מזרע"
9422 | "بیج_سے_دستخط_کلید_تخلیق"
9423 | "génération_clé_signature_depuis_graine"
9424 | "signaturschlüsselerzeugung_aus_saat"
9425 | "генерация_ключа_подписи_из_семени" => {
9426 let seed = hex_to_32(&self.arg_str(&args, 0, ""));
9427 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::from_seed(seed));
9428 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9429 },
9430 #[cfg(not(target_arch = "wasm32"))]
9431 "ed25519_public"
9432 | "서명공개키"
9433 | "กุญแจสาธารณะลายเซ็น"
9434 | "签名公钥"
9435 | "署名公開鍵"
9436 | "کلید_عمومی_امضا"
9437 | "مفتاح_التوقيع_العام"
9438 | "מפתח_חתימה_ציבורי"
9439 | "دستخط_عوامی_کلید"
9440 | "clé_publique_signature"
9441 | "signatur_öffentlicher_schlüssel"
9442 | "публичный_ключ_подписи" => {
9443 let h = self.arg_num(&args, 0, 0.0)? as usize;
9444 let pk = self
9445 .ed25519_ids
9446 .get(h)
9447 .map(|kp| hex_encode(&kp.public_key()))
9448 .unwrap_or_default();
9449 return Ok(Value::Str(pk));
9450 },
9451 #[cfg(not(target_arch = "wasm32"))]
9453 "ed25519_sign"
9454 | "서명하다"
9455 | "เซ็นชื่อ"
9456 | "签名"
9457 | "署名する"
9458 | "امضا_کردن"
9459 | "توقيع"
9460 | "לחתום"
9461 | "دستخط_کریں"
9462 | "signer"
9463 | "signieren"
9464 | "подписать" => {
9465 let h = self.arg_num(&args, 0, 0.0)? as usize;
9466 let msg = self.arg_str(&args, 1, "");
9467 let sig = self
9468 .ed25519_ids
9469 .get(h)
9470 .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
9471 .unwrap_or_default();
9472 return Ok(Value::Str(sig));
9473 },
9474 #[cfg(not(target_arch = "wasm32"))]
9476 "ed25519_verify"
9477 | "서명확인"
9478 | "ยืนยันลายเซ็น"
9479 | "验证签名"
9480 | "署名検証"
9481 | "تایید_امضا"
9482 | "التحقق_من_التوقيع"
9483 | "אימות_חתימה"
9484 | "دستخط_تصدیق"
9485 | "vérifier_signature"
9486 | "signatur_verifizieren"
9487 | "проверить_подпись" => {
9488 let pk_hex = self.arg_str(&args, 0, "");
9489 let msg = self.arg_str(&args, 1, "");
9490 let sig_hex = self.arg_str(&args, 2, "");
9491 let pk_bytes = hex_decode(&pk_hex);
9492 let sig_bytes = hex_decode(&sig_hex);
9493 let ok = (|| {
9494 let pk: [u8; 32] = pk_bytes.try_into().ok()?;
9495 let sig: [u8; 64] = sig_bytes.try_into().ok()?;
9496 Some(ling_crypto::Ed25519Keypair::verify(&pk, msg.as_bytes(), &sig).is_ok())
9497 })()
9498 .unwrap_or(false);
9499 return Ok(Value::Bool(ok));
9500 },
9501 #[cfg(not(target_arch = "wasm32"))]
9508 "mldsa_keygen"
9509 | "양자서명키생성"
9510 | "สร้างกุญแจลายเซ็นควอนตัม"
9511 | "生成量子签名密钥"
9512 | "量子署名鍵生成"
9513 | "تولید_کلید_امضای_کوانتومی"
9514 | "توليد_مفتاح_التوقيع_الكمي"
9515 | "יצירת_מפתח_חתימה_קוונטי"
9516 | "کوانٹم_دستخط_کلید_تخلیق"
9517 | "génération_clé_signature_quantique"
9518 | "quantensignaturschlüsselerzeugung"
9519 | "генерация_ключа_квантовой_подписи" => {
9520 self.mldsa_ids.push(ling_crypto::MlDsa65Keypair::generate());
9521 return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
9522 },
9523 #[cfg(not(target_arch = "wasm32"))]
9526 "mldsa_keygen_from_seed"
9527 | "씨앗에서양자서명키생성"
9528 | "สร้างกุญแจลายเซ็นควอนตัมจากเมล็ด"
9529 | "从种子生成量子签名密钥"
9530 | "シードから量子署名鍵生成"
9531 | "تولید_کلید_امضای_کوانتومی_از_دانه"
9532 | "توليد_مفتاح_التوقيع_الكمي_من_البذرة"
9533 | "יצירת_מפתח_חתימה_קוונטי_מזרע"
9534 | "بیج_سے_کوانٹم_دستخط_کلید_تخلیق"
9535 | "génération_clé_signature_quantique_depuis_graine"
9536 | "quantensignaturschlüsselerzeugung_aus_saat"
9537 | "генерация_ключа_квантовой_подписи_из_семени" => {
9538 let seed = hex_to_32(&self.arg_str(&args, 0, ""));
9539 self.mldsa_ids.push(ling_crypto::MlDsa65Keypair::from_seed(seed));
9540 return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
9541 },
9542 #[cfg(not(target_arch = "wasm32"))]
9543 "mldsa_public"
9544 | "양자서명공개키"
9545 | "กุญแจสาธารณะลายเซ็นควอนตัม"
9546 | "量子签名公钥"
9547 | "量子署名公開鍵"
9548 | "کلید_عمومی_امضای_کوانتومی"
9549 | "مفتاح_التوقيع_الكمي_العام"
9550 | "מפתח_חתימה_קוונטי_ציבורי"
9551 | "کوانٹم_دستخط_عوامی_کلید"
9552 | "clé_publique_signature_quantique"
9553 | "quantensignatur_öffentlicher_schlüssel"
9554 | "публичный_ключ_квантовой_подписи" => {
9555 let h = self.arg_num(&args, 0, 0.0)? as usize;
9556 let pk = self
9557 .mldsa_ids
9558 .get(h)
9559 .map(|kp| hex_encode(&kp.public_key()))
9560 .unwrap_or_default();
9561 return Ok(Value::Str(pk));
9562 },
9563 #[cfg(not(target_arch = "wasm32"))]
9565 "mldsa_sign"
9566 | "양자서명하다"
9567 | "เซ็นชื่อควอนตัม"
9568 | "量子签名"
9569 | "量子署名する"
9570 | "امضای_کوانتومی_کردن"
9571 | "توقيع_كمي"
9572 | "לחתום_קוונטית"
9573 | "کوانٹم_دستخط_کریں"
9574 | "signer_quantique"
9575 | "quantensignieren"
9576 | "подписать_квантово" => {
9577 let h = self.arg_num(&args, 0, 0.0)? as usize;
9578 let msg = self.arg_str(&args, 1, "");
9579 let sig = self
9580 .mldsa_ids
9581 .get(h)
9582 .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
9583 .unwrap_or_default();
9584 return Ok(Value::Str(sig));
9585 },
9586 #[cfg(not(target_arch = "wasm32"))]
9588 "mldsa_verify"
9589 | "양자서명확인"
9590 | "ยืนยันลายเซ็นควอนตัม"
9591 | "验证量子签名"
9592 | "量子署名検証"
9593 | "تایید_امضای_کوانتومی"
9594 | "التحقق_من_التوقيع_الكمي"
9595 | "אימות_חתימה_קוונטית"
9596 | "کوانٹم_دستخط_تصدیق"
9597 | "vérifier_signature_quantique"
9598 | "quantensignatur_verifizieren"
9599 | "проверить_квантовую_подпись" => {
9600 let pk_hex = self.arg_str(&args, 0, "");
9601 let msg = self.arg_str(&args, 1, "");
9602 let sig_hex = self.arg_str(&args, 2, "");
9603 let pk_bytes = hex_decode(&pk_hex);
9604 let sig_bytes = hex_decode(&sig_hex);
9605 let ok = ling_crypto::MlDsa65Keypair::verify(&pk_bytes, msg.as_bytes(), &sig_bytes)
9606 .is_ok();
9607 return Ok(Value::Bool(ok));
9608 },
9609 #[cfg(not(target_arch = "wasm32"))]
9612 "password_hash" | "비밀번호해시" => {
9613 let pw = self.arg_str(&args, 0, "");
9614 let hash = ling_crypto::Argon2idParams::default()
9615 .hash_password(pw.as_bytes())
9616 .unwrap_or_default();
9617 return Ok(Value::Str(hash));
9618 },
9619 #[cfg(not(target_arch = "wasm32"))]
9620 "password_verify" | "비밀번호확인" => {
9621 let pw = self.arg_str(&args, 0, "");
9622 let hash = self.arg_str(&args, 1, "");
9623 let ok = ling_crypto::Argon2idParams::verify_password(pw.as_bytes(), &hash).is_ok();
9624 return Ok(Value::Bool(ok));
9625 },
9626 #[cfg(not(target_arch = "wasm32"))]
9629 "random_hex" | "무작위16진수" => {
9630 use rand::RngCore;
9631 let n = self.arg_num(&args, 0, 16.0)?.max(0.0) as usize;
9632 let mut buf = vec![0u8; n];
9633 rand::rngs::OsRng.fill_bytes(&mut buf);
9634 return Ok(Value::Str(hex_encode(&buf)));
9635 },
9636 #[cfg(not(target_arch = "wasm32"))]
9640 "base64_encode" | "base64인코딩" => {
9641 use base64::Engine as _;
9642 let s = self.arg_str(&args, 0, "");
9643 return Ok(Value::Str(
9644 base64::engine::general_purpose::STANDARD.encode(s.as_bytes()),
9645 ));
9646 },
9647 #[cfg(not(target_arch = "wasm32"))]
9651 "base64_decode_to_file" | "base64파일로저장" => {
9652 use base64::Engine as _;
9653 let b64 = self.arg_str(&args, 0, "");
9654 let path = self.arg_str(&args, 1, "");
9655 let b64 = b64
9656 .split(',')
9657 .next_back()
9658 .unwrap_or(&b64); let ok = base64::engine::general_purpose::STANDARD
9660 .decode(b64)
9661 .ok()
9662 .and_then(|bytes| std::fs::write(&path, bytes).ok())
9663 .is_some();
9664 return Ok(Value::Bool(ok));
9665 },
9666 #[cfg(not(target_arch = "wasm32"))]
9671 "qr_svg" | "QR코드" => {
9672 let text = self.arg_str(&args, 0, "");
9673 let svg = qrcode::QrCode::new(text.as_bytes())
9674 .map(|code| {
9675 code.render::<qrcode::render::svg::Color>()
9676 .min_dimensions(240, 240)
9677 .dark_color(qrcode::render::svg::Color("#1a0f3d"))
9678 .light_color(qrcode::render::svg::Color("#ffffff"))
9679 .build()
9680 })
9681 .unwrap_or_default();
9682 return Ok(Value::Str(svg));
9683 },
9684 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9687 "zip_files" | "압축파일" => {
9688 let paths = match args.first() {
9689 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9690 _ => Vec::new(),
9691 };
9692 let out_path = self.arg_str(&args, 1, "out.zip");
9693 let ok = (|| -> std::io::Result<()> {
9694 let file = std::fs::File::create(&out_path)?;
9695 let mut writer = zip::ZipWriter::new(file);
9696 let options: zip::write::FileOptions<'_, ()> = zip::write::FileOptions::default()
9697 .compression_method(zip::CompressionMethod::Deflated);
9698 for p in &paths {
9699 let name = std::path::Path::new(p)
9700 .file_name()
9701 .map(|n| n.to_string_lossy().into_owned())
9702 .unwrap_or_else(|| p.clone());
9703 let bytes = std::fs::read(p)?;
9704 writer.start_file(name, options)?;
9705 std::io::Write::write_all(&mut writer, &bytes)?;
9706 }
9707 writer.finish()?;
9708 Ok(())
9709 })()
9710 .is_ok();
9711 return Ok(Value::Bool(ok));
9712 },
9713 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9720 "pdf_from_images" | "PDF来自图片" => {
9721 let paths = match args.first() {
9722 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9723 _ => Vec::new(),
9724 };
9725 let out_path = self.arg_str(&args, 1, "out.pdf");
9726 let ok = build_pdf_from_images(&paths, &out_path).is_ok();
9727 return Ok(Value::Bool(ok));
9728 },
9729
9730 "ease" => {
9734 let name = self.arg_str(&args, 0, "ease");
9735 let t = self.arg_num(&args, 1, 0.0)? as f32;
9736 return Ok(Value::Number(
9737 ling_ui::Easing::from_name(&name).apply(t) as f64
9738 ));
9739 },
9740
9741 "tween" | "补间" | "補間" | "트윈" | "แทรกค่า" | "میانفریم" | "تدرج_حركي" | "טווין" | "ٹوئین" | "твин" => {
9746 let a = self.arg_num(&args, 0, 0.0)?;
9747 let b = self.arg_num(&args, 1, 0.0)?;
9748 let t = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 1.0);
9749 return Ok(Value::Number(a + (b - a) * t));
9750 },
9751 "tween_ease" | "缓动补间" | "緩和補間" | "이징트윈" | "แทรกนุ่ม" | "میانفریم_نرم" | "تدرج_ناعم_حركي" | "טווין_חלק" | "ٹوئین_ایز" | "tween_lisse" | "tween_glättung" | "твин_плавность" =>
9752 {
9753 let a = self.arg_num(&args, 0, 0.0)? as f32;
9754 let b = self.arg_num(&args, 1, 0.0)? as f32;
9755 let t = self.arg_num(&args, 2, 0.0)? as f32;
9756 let kind = self.arg_str(&args, 3, "linear");
9757 let e = ling_animation::EaseFunction::from_name(&kind);
9758 return Ok(Value::Number(
9759 ling_animation::ease::tween_ease(&a, &b, t, e) as f64,
9760 ));
9761 },
9762 "breathe" | "呼吸" | "호흡" | "หายใจ" | "تنفس" | "נשימה" | "سانس" | "respirer" | "atmen" | "дышать" => {
9764 let t = self.arg_num(&args, 0, 0.0)? as f32;
9765 let rate = self.arg_num(&args, 1, 1.0)? as f32;
9766 let depth = self.arg_num(&args, 2, 0.1)? as f32;
9767 return Ok(Value::Number(
9768 ling_animation::scalar::breathe(t, rate, depth) as f64,
9769 ));
9770 },
9771 "wobble" | "摆动" | "揺れ" | "흔들림" | "โยก" | "نوسان" | "تذبذب" | "תנודה" | "لرزش" | "osciller" | "wackeln" | "покачивание" => {
9772 let t = self.arg_num(&args, 0, 0.0)? as f32;
9773 let freq = self.arg_num(&args, 1, 1.0)? as f32;
9774 let amp = self.arg_num(&args, 2, 1.0)? as f32;
9775 let phase = self.arg_num(&args, 3, 0.0)? as f32;
9776 return Ok(Value::Number(
9777 ling_animation::scalar::wobble(t, freq, amp, phase) as f64,
9778 ));
9779 },
9780 "gait_phase" | "步相" | "歩相" | "걸음위상" | "เฟสก้าว" | "فاز_گام" | "طور_المشية" | "שלב_הליכה" | "چال_مرحلہ" | "phase_démarche" | "gangphase" | "фаза_походки" => {
9781 let t = self.arg_num(&args, 0, 0.0)? as f32;
9782 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9783 return Ok(Value::Number(
9784 ling_animation::scalar::gait_phase(t, speed) as f64
9785 ));
9786 },
9787 "gait_swing" | "步摆" | "歩振り" | "걸음흔들" | "ก้าวแกว่ง" | "نوسان_گام" | "أرجحة_المشية" | "נדנוד_הליכה" | "چال_جھولا" | "balancement_démarche" | "gangschwung" | "мах_походки" =>
9788 {
9789 let t = self.arg_num(&args, 0, 0.0)? as f32;
9790 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9791 let stride = self.arg_num(&args, 2, 1.0)? as f32;
9792 return Ok(Value::Number(
9793 ling_animation::scalar::gait_swing(t, speed, stride) as f64,
9794 ));
9795 },
9796 "gait_lift" | "抬脚" | "足上げ" | "발들기" | "ยกเท้า" | "بلندشدن_گام" | "رفع_المشية" | "הרמת_הליכה" | "چال_اٹھاؤ" | "levée_démarche" | "ganghub" | "подъём_походки" => {
9797 let t = self.arg_num(&args, 0, 0.0)? as f32;
9798 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9799 let height = self.arg_num(&args, 2, 1.0)? as f32;
9800 return Ok(Value::Number(
9801 ling_animation::scalar::gait_lift(t, speed, height) as f64,
9802 ));
9803 },
9804 "spring_to" | "弹向" | "バネ寄せ" | "스프링이동" | "สปริงไป" | "فنر_بهسوی" | "نابض_إلى" | "קפיץ_אל" | "اسپرنگ_تک" | "ressort_vers" | "feder_zu" | "пружина_к" =>
9805 {
9806 let pos = self.arg_num(&args, 0, 0.0)? as f32;
9807 let vel = self.arg_num(&args, 1, 0.0)? as f32;
9808 let target = self.arg_num(&args, 2, 0.0)? as f32;
9809 let stiffness = self.arg_num(&args, 3, 120.0)? as f32;
9810 let damping = self.arg_num(&args, 4, 14.0)? as f32;
9811 let dt = self.arg_num(&args, 5, 1.0 / 60.0)? as f32;
9812 let (np, nv) =
9813 ling_animation::scalar::spring_step(pos, vel, target, stiffness, damping, dt);
9814 return Ok(Value::List(Rc::new(vec![
9815 Value::Number(np as f64),
9816 Value::Number(nv as f64),
9817 ])));
9818 },
9819 "ik2" | "反解" | "逆運動" | "역운동" | "ไอเค2" | "سینماتیک_معکوس2" | "حركية_عكسية2" | "קינמטיקה_הפוכה2" | "آئی_کے2" | "cinématique_inverse2" | "inverse_kinematik2" | "обратная_кинематика2" => {
9820 let l1 = self.arg_num(&args, 0, 1.0)? as f32;
9821 let l2 = self.arg_num(&args, 1, 1.0)? as f32;
9822 let tx = self.arg_num(&args, 2, 0.0)? as f32;
9823 let ty = self.arg_num(&args, 3, 0.0)? as f32;
9824 let (sh, el) = ling_animation::scalar::two_bone_ik(l1, l2, tx, ty);
9825 return Ok(Value::List(Rc::new(vec![
9826 Value::Number(sh as f64),
9827 Value::Number(el as f64),
9828 ])));
9829 },
9830 "gear_couple" | "齿轮联动" | "歯車連動" | "기어연동" | "เฟืองทด" | "جفت_چرخدنده" | "اقتران_التروس" | "צימוד_גלגלי_שיניים" | "گیئر_جوڑا" | "accoupler_engrenage" | "zahnrad_koppeln" | "сцепить_шестерни" =>
9832 {
9833 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9834 let ti = self.arg_num(&args, 1, 1.0)? as f32;
9835 let to = self.arg_num(&args, 2, 1.0)? as f32;
9836 return Ok(Value::Number(
9837 ling_animation::scalar::gear(angle, ti, to) as f64
9838 ));
9839 },
9840 "gear_train" | "齿轮组" | "歯車列" | "기어열" | "ชุดเฟือง" | "مجموعه_چرخدنده" | "قطار_التروس" | "שרשרת_גלגלי_שיניים" | "گیئر_ٹرین" | "train_engrenages" | "zahnradgetriebe" | "передача_шестерён" => {
9841 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9842 let teeth: Vec<f32> = match args.get(1) {
9843 Some(Value::List(items)) => items
9844 .iter()
9845 .filter_map(|v| {
9846 if let Value::Number(n) = v {
9847 Some(*n as f32)
9848 } else {
9849 None
9850 }
9851 })
9852 .collect(),
9853 _ => Vec::new(),
9854 };
9855 let out = ling_animation::mechanism::gear_train(angle, &teeth);
9856 return Ok(Value::List(Rc::new(
9857 out.into_iter().map(|a| Value::Number(a as f64)).collect(),
9858 )));
9859 },
9860 "cam_lift" | "凸轮升程" | "カム揚程" | "캠리프트" | "ยกลูกเบี้ยว" | "بلندشدن_بادامک" | "رفع_الكامة" | "הרמת_קאם" | "کیم_اٹھاؤ" | "levée_came" | "nockenhub" | "подъём_кулачка" =>
9861 {
9862 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9863 let lift = self.arg_num(&args, 1, 1.0)? as f32;
9864 return Ok(Value::Number(
9865 ling_animation::scalar::cam_lift(angle, lift) as f64
9866 ));
9867 },
9868 "piston" | "活塞" | "ピストン" | "피스톤" | "ลูกสูบ" | "پیستون" | "مكبس" | "בוכנה" | "پسٹن" | "kolben" | "поршень" => {
9869 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9870 let crank = self.arg_num(&args, 1, 1.0)? as f32;
9871 let rod = self.arg_num(&args, 2, 2.0)? as f32;
9872 return Ok(Value::Number(
9873 ling_animation::scalar::piston(angle, crank, rod) as f64,
9874 ));
9875 },
9876 "rack" | "齿条" | "ラック" | "랙" | "แร็ค" | "زبانهدنده" | "سكة_مسننة" | "מוט_שיניים" | "ریک" | "crémaillère" | "zahnstange" | "рейка" => {
9877 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9878 let radius = self.arg_num(&args, 1, 1.0)? as f32;
9879 return Ok(Value::Number(
9880 ling_animation::scalar::rack(angle, radius) as f64
9881 ));
9882 },
9883 #[cfg(not(target_arch = "wasm32"))]
9884 "mouse_x" => {
9885 let gfx = self.gfx.borrow();
9886 let v = gfx
9887 .window
9888 .as_ref()
9889 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9890 .map(|p| p.0 as f64)
9891 .unwrap_or(0.0);
9892 return Ok(Value::Number(v));
9893 },
9894 #[cfg(target_arch = "wasm32")]
9895 "mouse_x" => {
9896 return Ok(Value::Number(crate::gfx::wasm_mouse_x() as f64));
9897 },
9898 #[cfg(not(target_arch = "wasm32"))]
9899 "mouse_y" => {
9900 let gfx = self.gfx.borrow();
9901 let v = gfx
9902 .window
9903 .as_ref()
9904 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9905 .map(|p| p.1 as f64)
9906 .unwrap_or(0.0);
9907 return Ok(Value::Number(v));
9908 },
9909 #[cfg(target_arch = "wasm32")]
9910 "mouse_y" => {
9911 return Ok(Value::Number(crate::gfx::wasm_mouse_y() as f64));
9912 },
9913 #[cfg(not(target_arch = "wasm32"))]
9914 "mouse_down" => {
9915 let mut gfx = self.gfx.borrow_mut();
9916 let d = !gfx.input_suppressed()
9917 && gfx
9918 .window
9919 .as_ref()
9920 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
9921 .unwrap_or(false);
9922 return Ok(Value::Bool(d));
9923 },
9924 #[cfg(target_arch = "wasm32")]
9925 "mouse_down" => {
9926 return Ok(Value::Bool(crate::gfx::wasm_mouse_down()));
9927 },
9928 #[cfg(not(target_arch = "wasm32"))]
9929 "mouse_down_right" | "เมาส์ขวา" | "ماوس_راست_فشرده" | "الفأرة_اليمنى_مضغوطة" | "עכבר_ימני_לחוץ" | "دایاں_ماؤس_دبا_ہوا" => {
9930 let mut gfx = self.gfx.borrow_mut();
9931 let d = !gfx.input_suppressed()
9932 && gfx
9933 .window
9934 .as_ref()
9935 .map(|w| w.get_mouse_down(minifb::MouseButton::Right))
9936 .unwrap_or(false);
9937 return Ok(Value::Bool(d));
9938 },
9939 #[cfg(target_arch = "wasm32")]
9940 "mouse_down_right" | "เมาส์ขวา" | "ماوس_راست_فشرده" | "الفأرة_اليمنى_مضغوطة" | "עכבר_ימני_לחוץ" | "دایاں_ماؤس_دبا_ہوا" => {
9941 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_right()));
9942 },
9943 #[cfg(not(target_arch = "wasm32"))]
9944 "mouse_down_middle" | "เมาส์กลาง" | "ماوس_وسط_فشرده" | "الفأرة_الوسطى_مضغوطة" | "עכבר_אמצעי_לחוץ" | "درمیانی_ماؤس_دبا_ہوا" => {
9945 let mut gfx = self.gfx.borrow_mut();
9946 let d = !gfx.input_suppressed()
9947 && gfx
9948 .window
9949 .as_ref()
9950 .map(|w| w.get_mouse_down(minifb::MouseButton::Middle))
9951 .unwrap_or(false);
9952 return Ok(Value::Bool(d));
9953 },
9954 #[cfg(target_arch = "wasm32")]
9955 "mouse_down_middle" | "เมาส์กลาง" | "ماوس_وسط_فشرده" | "الفأرة_الوسطى_مضغوطة" | "עכבר_אמצעי_לחוץ" | "درمیانی_ماؤس_دبا_ہوا" => {
9956 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_middle()));
9957 },
9958 #[cfg(not(target_arch = "wasm32"))]
9959 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" | "ناحیه_فعال" | "منطقة_ساخنة" | "אזור_חם" | "ہاٹ_زون" | "survol_ui" | "ui_hover" | "ui_наведение" =>
9960 {
9961 let x = self.arg_num(&args, 0, 0.0)? as f32;
9962 let y = self.arg_num(&args, 1, 0.0)? as f32;
9963 let w = self.arg_num(&args, 2, 0.0)? as f32;
9964 let h = self.arg_num(&args, 3, 0.0)? as f32;
9965 let gfx = self.gfx.borrow();
9966 let (mx, my) = gfx
9967 .window
9968 .as_ref()
9969 .and_then(|win| win.get_mouse_pos(minifb::MouseMode::Clamp))
9970 .unwrap_or((0.0, 0.0));
9971 return Ok(Value::Bool(ling_ui::holo::hit_rect(mx, my, x, y, w, h)));
9972 },
9973 #[cfg(target_arch = "wasm32")]
9974 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" | "ناحیه_فعال" | "منطقة_ساخنة" | "אזור_חם" | "ہاٹ_زون" | "survol_ui" | "ui_hover" | "ui_наведение" =>
9975 {
9976 return Ok(Value::Bool(false));
9977 },
9978 "ui_text" | "界面文字" | "UI文字" | "UI텍스트" | "ข้อความหน้าจอ" | "متن_رابط" | "نص_الواجهة" | "טקסט_ממשק" | "یو_آئی_متن" | "texte_ui" | "ui_beschriftung" | "ui_текст" =>
9980 {
9981 let x = self.arg_num(&args, 0, 0.0)? as f32;
9982 let y = self.arg_num(&args, 1, 0.0)? as f32;
9983 let scale = self.arg_num(&args, 2, 16.0)? as f32;
9984 let s = self.arg_str(&args, 3, "");
9985 let segs = ling_ui::holo::text_lines(&s, x, y, scale * 0.62, scale, scale * 0.24);
9986 let mut gfx = self.gfx.borrow_mut();
9987 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
9988 for sg in segs {
9989 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
9990 }
9991 return Ok(Value::Unit);
9992 },
9993 #[cfg(not(target_arch = "wasm32"))]
9996 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" | "بارگذاری_فونت" | "تحميل_الخط" | "טעינת_גופן" | "فونٹ_لوڈ" | "charger_police" | "schriftart_laden" | "загрузить_шрифт" =>
9997 {
9998 let path = self.arg_str(&args, 0, "");
9999 let weight = match self.arg_num(&args, 1, 0.0)? {
10001 w if w > 0.0 => Some(w as f32),
10002 _ => None,
10003 };
10004 let mut loaded = ling_graphics::VectorFont::from_path_weight(&path, weight);
10006 if loaded.is_err() {
10007 if let Some(dir) = &self.source_dir {
10008 let joined = dir.join(&path);
10009 loaded = ling_graphics::VectorFont::from_path_weight(
10010 &joined.to_string_lossy(),
10011 weight,
10012 );
10013 }
10014 }
10015 match loaded {
10016 Ok(f) => {
10017 let id = self.fonts.len();
10018 self.fonts.push(f);
10019 return Ok(Value::Number(id as f64));
10020 },
10021 Err(e) => {
10022 eprintln!("font_load failed ({path}): {e}");
10023 return Ok(Value::Number(-1.0));
10024 },
10025 }
10026 },
10027 #[cfg(target_arch = "wasm32")]
10028 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" | "بارگذاری_فونت" | "تحميل_الخط" | "טעינת_גופן" | "فونٹ_لوڈ" | "charger_police" | "schriftart_laden" | "загрузить_шрифт" =>
10029 {
10030 return Ok(Value::Number(-1.0));
10033 },
10034 #[cfg(not(target_arch = "wasm32"))]
10039 "image_load" =>
10040 {
10041 let path = self.arg_str(&args, 0, "").replace('\\', "/");
10042 let mut loaded = image::open(&path);
10043 if loaded.is_err() {
10044 if let Some(dir) = &self.source_dir {
10045 let joined = dir.join(&path);
10046 loaded = image::open(&joined);
10047 }
10048 }
10049 match loaded {
10050 Ok(img) => {
10051 let id = self.images.len();
10052 self.images.push(img.to_rgba8());
10053 return Ok(Value::Number(id as f64));
10054 },
10055 Err(e) => {
10056 eprintln!("image_load failed ({path}): {e}");
10057 return Ok(Value::Number(-1.0));
10058 },
10059 }
10060 },
10061 #[cfg(target_arch = "wasm32")]
10062 "image_load" =>
10063 {
10064 return Ok(Value::Number(-1.0));
10066 },
10067 "image_width" =>
10068 {
10069 let id = self.arg_num(&args, 0, -1.0)? as i64;
10070 if id >= 0 && (id as usize) < self.images.len() {
10071 return Ok(Value::Number(self.images[id as usize].width() as f64));
10072 }
10073 return Ok(Value::Number(0.0));
10074 },
10075 "image_height" =>
10076 {
10077 let id = self.arg_num(&args, 0, -1.0)? as i64;
10078 if id >= 0 && (id as usize) < self.images.len() {
10079 return Ok(Value::Number(self.images[id as usize].height() as f64));
10080 }
10081 return Ok(Value::Number(0.0));
10082 },
10083 "image_pixel_r" | "image_pixel_g" | "image_pixel_b" | "image_pixel_a" =>
10084 {
10085 let id = self.arg_num(&args, 0, -1.0)? as i64;
10086 let px = self.arg_num(&args, 1, 0.0)? as i64;
10087 let py = self.arg_num(&args, 2, 0.0)? as i64;
10088 if id >= 0 && (id as usize) < self.images.len() {
10089 let img = &self.images[id as usize];
10090 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
10091 let p = img.get_pixel(px as u32, py as u32);
10092 let ch = match name {
10093 "image_pixel_r" => p[0],
10094 "image_pixel_g" => p[1],
10095 "image_pixel_b" => p[2],
10096 _ => p[3],
10097 };
10098 return Ok(Value::Number(ch as f64));
10099 }
10100 }
10101 return Ok(Value::Number(0.0));
10102 },
10103 "image_new" =>
10112 {
10113 let w = self.arg_num(&args, 0, 1.0)?.max(1.0) as u32;
10114 let h = self.arg_num(&args, 1, 1.0)?.max(1.0) as u32;
10115 let id = self.images.len();
10116 self.images.push(image::RgbaImage::new(w, h));
10117 return Ok(Value::Number(id as f64));
10118 },
10119 "image_set_pixel" =>
10124 {
10125 let id = self.arg_num(&args, 0, -1.0)? as i64;
10126 let px = self.arg_num(&args, 1, 0.0)? as i64;
10127 let py = self.arg_num(&args, 2, 0.0)? as i64;
10128 let r = self.arg_num(&args, 3, 0.0)?.clamp(0.0, 255.0) as u8;
10129 let g = self.arg_num(&args, 4, 0.0)?.clamp(0.0, 255.0) as u8;
10130 let b = self.arg_num(&args, 5, 0.0)?.clamp(0.0, 255.0) as u8;
10131 let a = self.arg_num(&args, 6, 255.0)?.clamp(0.0, 255.0) as u8;
10132 if id >= 0 && (id as usize) < self.images.len() {
10133 let img = &mut self.images[id as usize];
10134 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
10135 img.put_pixel(px as u32, py as u32, image::Rgba([r, g, b, a]));
10136 }
10137 }
10138 return Ok(Value::Unit);
10139 },
10140 #[cfg(not(target_arch = "wasm32"))]
10148 "image_save" =>
10149 {
10150 let id = self.arg_num(&args, 0, -1.0)? as i64;
10151 let path = self.arg_str(&args, 1, "");
10152 if id >= 0 && (id as usize) < self.images.len() {
10153 if let Some(parent) = std::path::Path::new(&path).parent() {
10154 if !parent.as_os_str().is_empty() {
10155 let _ = std::fs::create_dir_all(parent);
10156 }
10157 }
10158 if self.images[id as usize].save(&path).is_ok() {
10159 return Ok(Value::Number(1.0));
10160 }
10161 }
10162 return Ok(Value::Number(-1.0));
10163 },
10164 #[cfg(target_arch = "wasm32")]
10165 "image_save" =>
10166 {
10167 return Ok(Value::Number(-1.0));
10168 },
10169 #[cfg(not(target_arch = "wasm32"))]
10179 "image_draw" =>
10180 {
10181 let id = self.arg_num(&args, 0, -1.0)? as i64;
10182 let dx = self.arg_num(&args, 1, 0.0)? as i32;
10183 let dy = self.arg_num(&args, 2, 0.0)? as i32;
10184 let dw = self.arg_num(&args, 3, 0.0)?.max(0.0) as i32;
10185 let dh = self.arg_num(&args, 4, 0.0)?.max(0.0) as i32;
10186 if id >= 0 && (id as usize) < self.images.len() && dw > 0 && dh > 0 {
10187 let img = &self.images[id as usize];
10188 let sw = img.width() as i32;
10189 let sh = img.height() as i32;
10190 if sw > 0 && sh > 0 {
10191 let mut gfx = self.gfx.borrow_mut();
10192 let (fw, fh) = (gfx.width as i32, gfx.height as i32);
10193 for py in 0..dh {
10194 let ty = dy + py;
10195 if ty < 0 || ty >= fh {
10196 continue;
10197 }
10198 let sy = (py * sh / dh).clamp(0, sh - 1) as u32;
10199 for px in 0..dw {
10200 let tx = dx + px;
10201 if tx < 0 || tx >= fw {
10202 continue;
10203 }
10204 let sx = (px * sw / dw).clamp(0, sw - 1) as u32;
10205 let p = img.get_pixel(sx, sy);
10206 let a = p[3] as u32;
10207 if a == 0 {
10208 continue;
10209 }
10210 let idx = ty as usize * gfx.width + tx as usize;
10211 if a >= 255 {
10212 gfx.buffer[idx] =
10213 ((p[0] as u32) << 16) | ((p[1] as u32) << 8) | (p[2] as u32);
10214 } else {
10215 let bg = gfx.buffer[idx];
10216 let br = (bg >> 16) & 0xff;
10217 let bg_g = (bg >> 8) & 0xff;
10218 let bb = bg & 0xff;
10219 let r = (p[0] as u32 * a + br * (255 - a)) / 255;
10220 let g = (p[1] as u32 * a + bg_g * (255 - a)) / 255;
10221 let b = (p[2] as u32 * a + bb * (255 - a)) / 255;
10222 gfx.buffer[idx] = (r << 16) | (g << 8) | b;
10223 }
10224 }
10225 }
10226 }
10227 }
10228 return Ok(Value::Unit);
10229 },
10230 #[cfg(target_arch = "wasm32")]
10231 "image_draw" =>
10232 {
10233 return Ok(Value::Unit);
10234 },
10235 #[cfg(not(target_arch = "wasm32"))]
10238 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" | "متن_فونت" | "نص_الخط" | "טקסט_גופן" | "فونٹ_متن" | "texte_police" | "schriftart_text" | "текст_шрифт" =>
10239 {
10240 let id = self.arg_num(&args, 0, 0.0)? as i64;
10241 let x = self.arg_num(&args, 1, 0.0)? as f32;
10242 let y = self.arg_num(&args, 2, 0.0)? as f32;
10243 let px = self.arg_num(&args, 3, 16.0)? as f32;
10244 let s = self.arg_str(&args, 4, "");
10245 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
10246 let strokes = self.font_layout_2d(id as usize, x, y, px, &s);
10247 let mut gfx = self.gfx.borrow_mut();
10248 let (w, h, color, add, aa) =
10249 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
10250 for pl in &strokes {
10251 for seg in pl.windows(2) {
10252 if aa {
10253 crate::gfx::raster::draw_line_aa(
10254 &mut gfx.buffer,
10255 w,
10256 h,
10257 color,
10258 add,
10259 seg[0][0],
10260 seg[0][1],
10261 seg[1][0],
10262 seg[1][1],
10263 );
10264 } else {
10265 crate::gfx::raster::draw_line(
10266 &mut gfx.buffer,
10267 w,
10268 h,
10269 color,
10270 seg[0][0],
10271 seg[0][1],
10272 seg[1][0],
10273 seg[1][1],
10274 );
10275 }
10276 }
10277 }
10278 }
10279 return Ok(Value::Unit);
10280 },
10281 #[cfg(target_arch = "wasm32")]
10282 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" | "متن_فونت" | "نص_الخط" | "טקסט_גופן" | "فونٹ_متن" | "texte_police" | "schriftart_text" | "текст_шрифт" =>
10283 {
10284 return Ok(Value::Unit);
10285 },
10286 #[cfg(not(target_arch = "wasm32"))]
10289 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" | "پرکردن_متن_فونت" | "تعبئة_نص_الخط" | "מילוי_טקסט_גופן" | "فونٹ_متن_بھرو" | "remplir_texte_police" | "schriftart_text_füllen" | "заполнить_текст_шрифт" =>
10290 {
10291 let id = self.arg_num(&args, 0, 0.0)? as i64;
10292 let x = self.arg_num(&args, 1, 0.0)? as f32;
10293 let y = self.arg_num(&args, 2, 0.0)? as f32;
10294 let px = self.arg_num(&args, 3, 16.0)? as f32;
10295 let s = self.arg_str(&args, 4, "");
10296 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
10297 let glyphs = self.font_layout_2d_glyphs(id as usize, x, y, px, &s);
10299 let mut gfx = self.gfx.borrow_mut();
10300 let (w, h, color, add, aa) =
10301 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
10302 for contours in &glyphs {
10303 if aa {
10304 crate::gfx::raster::fill_contours_aa(
10305 &mut gfx.buffer,
10306 w,
10307 h,
10308 color,
10309 add,
10310 contours,
10311 );
10312 } else {
10313 crate::gfx::raster::fill_contours(
10314 &mut gfx.buffer,
10315 w,
10316 h,
10317 color,
10318 add,
10319 contours,
10320 );
10321 }
10322 }
10323 }
10324 return Ok(Value::Unit);
10325 },
10326 #[cfg(target_arch = "wasm32")]
10327 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" | "پرکردن_متن_فونت" | "تعبئة_نص_الخط" | "מילוי_טקסט_גופן" | "فونٹ_متن_بھرو" | "remplir_texte_police" | "schriftart_text_füllen" | "заполнить_текст_шрифт" =>
10328 {
10329 return Ok(Value::Unit);
10330 },
10331 #[cfg(not(target_arch = "wasm32"))]
10335 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" | "متن_فونت_سهبعدی" | "نص_خط_ثلاثي_الأبعاد" | "טקסט_גופן_תלת_ממדי" | "تھری_ڈی_فونٹ_متن" | "texte_police_3d" | "schriftart_text_3d" | "текст_шрифт_3d" =>
10336 {
10337 let id = self.arg_num(&args, 0, 0.0)? as i64;
10338 let cx = self.arg_num(&args, 1, 0.0)? as f32;
10339 let cy = self.arg_num(&args, 2, 0.0)? as f32;
10340 let cz = self.arg_num(&args, 3, 0.0)? as f32;
10341 let ux = self.arg_num(&args, 4, 1.0)? as f32;
10342 let uy = self.arg_num(&args, 5, 0.0)? as f32;
10343 let uz = self.arg_num(&args, 6, 0.0)? as f32;
10344 let vx = self.arg_num(&args, 7, 0.0)? as f32;
10345 let vy = self.arg_num(&args, 8, 1.0)? as f32;
10346 let vz = self.arg_num(&args, 9, 0.0)? as f32;
10347 let size = self.arg_num(&args, 10, 1.0)? as f32;
10348 let s = self.arg_str(&args, 11, "");
10349 let fill_rows = self.arg_num(&args, 12, 0.0)? as i32;
10353 if id >= 0 && (id as usize) < self.fonts.len() && size > 0.0 {
10354 let font = &mut self.fonts[id as usize];
10356 let asc = font.ascent();
10357 let mut pen = 0.0f32;
10358 let mut lines: Vec<[f32; 6]> = Vec::new();
10359 for ch in s.chars() {
10360 let go = font.glyph_outline(ch, 0.01);
10361 let map = |p: [f32; 2], pen: f32| {
10362 let a = pen + p[0];
10363 let b = p[1] - asc; [
10365 cx + a * size * ux + b * size * vx,
10366 cy + a * size * uy + b * size * vy,
10367 cz + a * size * uz + b * size * vz,
10368 ]
10369 };
10370 for pl in &go.polylines {
10371 for seg in pl.windows(2) {
10372 let p0 = map(seg[0], pen);
10373 let p1 = map(seg[1], pen);
10374 lines.push([p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]]);
10375 }
10376 }
10377 if fill_rows > 0 {
10378 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
10382 for pl in &go.polylines {
10383 for p in pl {
10384 ymin = ymin.min(p[1]);
10385 ymax = ymax.max(p[1]);
10386 }
10387 }
10388 if ymax > ymin {
10389 for r in 0..fill_rows {
10390 let y =
10391 ymin + (r as f32 + 0.5) * (ymax - ymin) / fill_rows as f32;
10392 let mut xs: Vec<f32> = Vec::new();
10393 for pl in &go.polylines {
10394 let n = pl.len();
10395 if n < 2 {
10396 continue;
10397 }
10398 for k in 0..n {
10399 let p0 = pl[k];
10400 let p1 = pl[(k + 1) % n];
10401 if k + 1 == n
10402 && (p1[0] - p0[0]).abs() < 1e-6
10403 && (p1[1] - p0[1]).abs() < 1e-6
10404 {
10405 continue; }
10407 let (y0, y1) = (p0[1], p1[1]);
10408 if (y0 <= y && y1 > y) || (y1 <= y && y0 > y) {
10409 let t = (y - y0) / (y1 - y0);
10410 xs.push(p0[0] + t * (p1[0] - p0[0]));
10411 }
10412 }
10413 }
10414 xs.sort_by(|a, b| {
10415 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
10416 });
10417 let mut k = 0;
10418 while k + 1 < xs.len() {
10419 let a = map([xs[k], y], pen);
10420 let b = map([xs[k + 1], y], pen);
10421 lines.push([a[0], a[1], a[2], b[0], b[1], b[2]]);
10422 k += 2;
10423 }
10424 }
10425 }
10426 }
10427 pen += go.advance;
10428 }
10429 let mut gfx = self.gfx.borrow_mut();
10430 let color = gfx.color;
10431 let near = -gfx.camera.zdist + 0.05;
10432 for l in &lines {
10433 let (mut ax, mut ay, mut az) = (l[0], l[1], l[2]);
10434 let (mut bx, mut by, mut bz) = (l[3], l[4], l[5]);
10435 let da = gfx.camera.depth(ax, ay, az);
10436 let db = gfx.camera.depth(bx, by, bz);
10437 if da <= near && db <= near {
10438 continue;
10439 }
10440 if da <= near {
10441 let t = (near - da) / (db - da);
10442 ax += t * (bx - ax);
10443 ay += t * (by - ay);
10444 az += t * (bz - az);
10445 } else if db <= near {
10446 let t = (near - da) / (db - da);
10447 bx = ax + t * (bx - ax);
10448 by = ay + t * (by - ay);
10449 bz = az + t * (bz - az);
10450 }
10451 let (sax, say, da2) = gfx.camera.project(ax, ay, az);
10452 let (sbx, sby, db2) = gfx.camera.project(bx, by, bz);
10453 let depth = (da2 + db2) / 2.0;
10454 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
10455 }
10456 }
10457 return Ok(Value::Unit);
10458 },
10459 #[cfg(target_arch = "wasm32")]
10460 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" | "متن_فونت_سهبعدی" | "نص_خط_ثلاثي_الأبعاد" | "טקסט_גופן_תלת_ממדי" | "تھری_ڈی_فونٹ_متن" | "texte_police_3d" | "schriftart_text_3d" | "текст_шрифт_3d" =>
10461 {
10462 return Ok(Value::Unit);
10463 },
10464 #[cfg(not(target_arch = "wasm32"))]
10466 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" | "عرض_فونت" | "عرض_الخط" | "רוחב_גופן" | "فونٹ_چوڑائی" | "largeur_police" | "schriftart_breite" | "ширина_шрифта" =>
10467 {
10468 let id = self.arg_num(&args, 0, 0.0)? as i64;
10469 let px = self.arg_num(&args, 1, 16.0)? as f32;
10470 let s = self.arg_str(&args, 2, "");
10471 if id >= 0 && (id as usize) < self.fonts.len() {
10472 return Ok(Value::Number(self.fonts[id as usize].measure(&s, px) as f64));
10473 }
10474 return Ok(Value::Number(0.0));
10475 },
10476 #[cfg(target_arch = "wasm32")]
10477 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" | "عرض_فونت" | "عرض_الخط" | "רוחב_גופן" | "فونٹ_چوڑائی" | "largeur_police" | "schriftart_breite" | "ширина_шрифта" =>
10478 {
10479 return Ok(Value::Number(0.0));
10480 },
10481 #[cfg(not(target_arch = "wasm32"))]
10486 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10487 | "フォント輪郭" | "글꼴윤곽" | "خطدور_نویسه_فونت" | "حدود_حرف_الخط" | "קו_מתאר_גליף" | "فونٹ_گلف_آؤٹ_لائن" => {
10488 let id = self.arg_num(&args, 0, 0.0)? as i64;
10489 let s = self.arg_str(&args, 1, "");
10490 let tol = self.arg_num(&args, 2, 0.01)? as f32;
10491 let ch = s.chars().next().unwrap_or(' ');
10492 if id >= 0 && (id as usize) < self.fonts.len() {
10493 let go = self.fonts[id as usize].glyph_outline(ch, tol.max(1e-4));
10494 let mut contours: Vec<Value> = Vec::with_capacity(go.polylines.len());
10495 for pl in &go.polylines {
10496 let mut flat: Vec<Value> = Vec::with_capacity(pl.len() * 2);
10497 for p in pl {
10498 flat.push(Value::Number(p[0] as f64));
10499 flat.push(Value::Number(p[1] as f64));
10500 }
10501 contours.push(Value::List(Rc::new(flat)));
10502 }
10503 return Ok(Value::List(Rc::new(contours)));
10504 }
10505 return Ok(Value::List(Rc::new(vec![])));
10506 },
10507 #[cfg(target_arch = "wasm32")]
10508 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10509 | "フォント輪郭" | "글꼴윤곽" | "خطدور_نویسه_فونت" | "حدود_حرف_الخط" | "קו_מתאר_גליף" | "فونٹ_گلف_آؤٹ_لائن" => {
10510 return Ok(Value::List(Rc::new(vec![])));
10511 },
10512 #[cfg(not(target_arch = "wasm32"))]
10515 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" | "پیشروی_فونت" | "تقدم_الخط" | "קידום_גופן" | "فونٹ_ایڈوانس" => {
10516 let id = self.arg_num(&args, 0, 0.0)? as i64;
10517 let s = self.arg_str(&args, 1, "");
10518 let ch = s.chars().next().unwrap_or(' ');
10519 if id >= 0 && (id as usize) < self.fonts.len() {
10520 return Ok(Value::Number(self.fonts[id as usize].advance(ch) as f64));
10521 }
10522 return Ok(Value::Number(0.0));
10523 },
10524 #[cfg(target_arch = "wasm32")]
10525 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" | "پیشروی_فونت" | "تقدم_الخط" | "קידום_גופן" | "فونٹ_ایڈوانس" => {
10526 return Ok(Value::Number(0.0));
10527 },
10528
10529 "ui_frame" | "边框" | "フレーム枠" | "프레임틀" | "กรอบ" | "قاب_رابط" | "إطار_الواجهة" | "מסגרת_ממשק" | "یو_آئی_فریم" | "cadre_ui" | "ui_rahmen" | "ui_рамка" => {
10531 let x = self.arg_num(&args, 0, 0.0)? as f32;
10532 let y = self.arg_num(&args, 1, 0.0)? as f32;
10533 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10534 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10535 let l = self.arg_num(&args, 4, 14.0)? as f32;
10536 let segs = ling_ui::holo::corner_brackets(x, y, w0, h0, l);
10537 let mut gfx = self.gfx.borrow_mut();
10538 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10539 for sg in segs {
10540 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10541 }
10542 return Ok(Value::Unit);
10543 },
10544 "ui_bevel" | "斜角框" | "ベベル枠" | "베벨틀" | "กรอบเฉียง" | "لبه_شیبدار" | "حافة_مشطوفة" | "מסגרת_משופעת" | "یو_آئی_بیول" | "biseau_ui" | "ui_fase" | "ui_фаска" =>
10546 {
10547 let x = self.arg_num(&args, 0, 0.0)? as f32;
10548 let y = self.arg_num(&args, 1, 0.0)? as f32;
10549 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10550 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10551 let bv = self.arg_num(&args, 4, 10.0)? as f32;
10552 let segs = ling_ui::holo::beveled_rect(x, y, w0, h0, bv);
10553 let mut gfx = self.gfx.borrow_mut();
10554 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10555 for sg in segs {
10556 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10557 }
10558 return Ok(Value::Unit);
10559 },
10560
10561 #[cfg(not(target_arch = "wasm32"))]
10567 "ui_theme" | "界面主题" | "UIテーマ" | "인터페이스테마" | "ธีมส่วนติดต่อ" | "پوسته_رابط" | "سمة_الواجهة" | "ערכת_נושא" | "یو_آئی_تھیم" | "thème_ui" | "ui_thema" | "ui_тема" =>
10568 {
10569 let cur = self.ui_theme;
10570 let primary = self.color_at(&args, 0, cur.primary);
10571 let accent = self.color_at(&args, 3, cur.accent);
10572 let track = self.color_at(&args, 6, cur.track);
10573 let warn = self.color_at(&args, 9, cur.warn);
10574 let text = self.color_at(&args, 12, cur.text);
10575 let bg = self.color_at(&args, 15, cur.bg);
10576 self.ui_theme = UiTheme { primary, accent, track, warn, text, bg };
10577 return Ok(Value::Unit);
10578 },
10579
10580 "ui_theme_colors" | "인터페이스테마색상" => {
10586 let th = self.ui_theme;
10587 let mut out = Vec::with_capacity(18);
10588 for c in [th.primary, th.accent, th.track, th.warn, th.text, th.bg] {
10589 out.push(Value::Number(((c >> 16) & 0xFF) as f64));
10590 out.push(Value::Number(((c >> 8) & 0xFF) as f64));
10591 out.push(Value::Number((c & 0xFF) as f64));
10592 }
10593 return Ok(Value::List(Rc::new(out)));
10594 },
10595
10596 #[cfg(not(target_arch = "wasm32"))]
10598 "ui_radar" | "雷达" | "レーダー" | "레이더" | "เรดาร์" | "رادار_رابط" | "رادار_الواجهة" | "מכ״ם_ממשק" | "یو_آئی_ریڈار" | "radar_ui" | "ui_радар" => {
10599 let cx = self.arg_num(&args, 0, 0.)? as f32;
10600 let cy = self.arg_num(&args, 1, 0.)? as f32;
10601 let r = self.arg_num(&args, 2, 60.)? as f32;
10602 let sweep = self.arg_num(&args, 3, 0.)? as f32;
10603 let th = self.ui_theme;
10604 let prim = self.color_at(&args, 4, th.primary);
10605 self.draw_ui(&ling_ui::widgets::radar(
10606 cx, cy, r, sweep, prim, th.accent, th.track,
10607 ));
10608 return Ok(Value::Unit);
10609 },
10610 #[cfg(not(target_arch = "wasm32"))]
10611 "ui_compass" | "罗盘" | "コンパス" | "나침반" | "เข็มทิศ" | "قطبنمای_رابط" | "بوصلة_الواجهة" | "מצפן_ממשק" | "یو_آئی_قطب_نما" | "boussole_ui" | "ui_kompass" | "ui_компас" => {
10612 let x = self.arg_num(&args, 0, 0.)? as f32;
10613 let y = self.arg_num(&args, 1, 0.)? as f32;
10614 let w0 = self.arg_num(&args, 2, 300.)? as f32;
10615 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10616 let head = self.arg_num(&args, 4, 0.)? as f32;
10617 let th = self.ui_theme;
10618 let prim = self.color_at(&args, 5, th.primary);
10619 self.draw_ui(&ling_ui::widgets::compass(
10620 x, y, w0, h0, head, prim, th.track,
10621 ));
10622 return Ok(Value::Unit);
10623 },
10624 #[cfg(not(target_arch = "wasm32"))]
10625 "ui_reticle" | "准星" | "照準" | "조준선" | "เป้าเล็ง" | "نشانه_رابط" | "علامة_تصويب" | "כוונת" | "نشانہ" | "réticule_ui" | "ui_fadenkreuz" | "ui_прицел" => {
10626 let cx = self.arg_num(&args, 0, 0.)? as f32;
10627 let cy = self.arg_num(&args, 1, 0.)? as f32;
10628 let r = self.arg_num(&args, 2, 30.)? as f32;
10629 let spread = self.arg_num(&args, 3, 0.)? as f32;
10630 let th = self.ui_theme;
10631 let prim = self.color_at(&args, 4, th.primary);
10632 self.draw_ui(&ling_ui::widgets::reticle(cx, cy, r, spread, prim));
10633 return Ok(Value::Unit);
10634 },
10635 #[cfg(not(target_arch = "wasm32"))]
10636 "ui_target" | "锁定框" | "ターゲット" | "표적" | "กรอบเป้า" | "قاب_هدف" | "إطار_الهدف" | "מסגרת_מטרה" | "ہدف_فریم" | "cible_ui" | "ui_ziel" | "ui_цель" =>
10637 {
10638 let x = self.arg_num(&args, 0, 0.)? as f32;
10639 let y = self.arg_num(&args, 1, 0.)? as f32;
10640 let w0 = self.arg_num(&args, 2, 80.)? as f32;
10641 let h0 = self.arg_num(&args, 3, 80.)? as f32;
10642 let lock = self.arg_num(&args, 4, 0.)? as f32;
10643 let th = self.ui_theme;
10644 let prim = self.color_at(&args, 5, th.primary);
10645 self.draw_ui(&ling_ui::widgets::target(
10646 x, y, w0, h0, lock, prim, th.accent,
10647 ));
10648 return Ok(Value::Unit);
10649 },
10650 #[cfg(not(target_arch = "wasm32"))]
10651 "ui_panel" | "面板" | "パネル" | "패널" | "แผง" | "پنل_رابط" | "لوحة_الواجهة" | "לוח_ממשק" | "یو_آئی_پینل" | "panneau_ui" | "ui_feld" | "ui_панель" => {
10652 let x = self.arg_num(&args, 0, 0.)? as f32;
10653 let y = self.arg_num(&args, 1, 0.)? as f32;
10654 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10655 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10656 let bv = self.arg_num(&args, 4, 12.)? as f32;
10657 let th = self.ui_theme;
10658 let prim = self.color_at(&args, 5, th.primary);
10659 self.draw_ui(&ling_ui::widgets::panel(x, y, w0, h0, bv, prim, th.bg));
10660 return Ok(Value::Unit);
10661 },
10662 #[cfg(not(target_arch = "wasm32"))]
10663 "ui_scanlines" | "扫描线" | "走査線" | "스캔라인" | "เส้นสแกน" | "خطوط_اسکن" | "خطوط_المسح" | "קווי_סריקה" | "اسکین_لائنز" | "lignes_balayage_ui" | "ui_abtastzeilen" | "ui_линии_развёртки" =>
10664 {
10665 let x = self.arg_num(&args, 0, 0.)? as f32;
10666 let y = self.arg_num(&args, 1, 0.)? as f32;
10667 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10668 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10669 let dens = self.arg_num(&args, 4, 24.)? as usize;
10670 let th = self.ui_theme;
10671 let line = self.color_at(&args, 5, th.track);
10672 self.draw_ui(&ling_ui::widgets::scanlines(x, y, w0, h0, dens, line));
10673 return Ok(Value::Unit);
10674 },
10675
10676 #[cfg(not(target_arch = "wasm32"))]
10678 "ui_bar" | "进度条" | "バー" | "막대" | "แถบ" | "نوار_رابط" | "شريط_الواجهة" | "סרגל_ממשק" | "یو_آئی_بار" | "barre_ui" | "ui_leiste" | "ui_полоса" => {
10679 let x = self.arg_num(&args, 0, 0.)? as f32;
10680 let y = self.arg_num(&args, 1, 0.)? as f32;
10681 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10682 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10683 let val = self.arg_num(&args, 4, 0.)? as f32;
10684 let max = self.arg_num(&args, 5, 1.)? as f32;
10685 let th = self.ui_theme;
10686 let fill = self.color_at(&args, 6, th.primary);
10687 self.draw_ui(&ling_ui::widgets::bar(
10688 x,
10689 y,
10690 w0,
10691 h0,
10692 val / max.max(1e-6),
10693 fill,
10694 th.track,
10695 ));
10696 return Ok(Value::Unit);
10697 },
10698 #[cfg(not(target_arch = "wasm32"))]
10699 "ui_segbar" | "分段条" | "分割バー" | "분할막대" | "แถบแบ่ง" | "نوار_قطعهای" | "شريط_مقسم" | "סרגל_מקוטע" | "سیگمنٹ_بار" | "barre_segmentée_ui" | "ui_segmentleiste" | "ui_сегментная_полоса" =>
10700 {
10701 let x = self.arg_num(&args, 0, 0.)? as f32;
10702 let y = self.arg_num(&args, 1, 0.)? as f32;
10703 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10704 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10705 let val = self.arg_num(&args, 4, 0.)? as f32;
10706 let max = self.arg_num(&args, 5, 1.)? as f32;
10707 let segs = self.arg_num(&args, 6, 10.)? as usize;
10708 let th = self.ui_theme;
10709 let fill = self.color_at(&args, 7, th.primary);
10710 self.draw_ui(&ling_ui::widgets::segbar(
10711 x,
10712 y,
10713 w0,
10714 h0,
10715 val / max.max(1e-6),
10716 segs,
10717 fill,
10718 th.track,
10719 ));
10720 return Ok(Value::Unit);
10721 },
10722 #[cfg(not(target_arch = "wasm32"))]
10723 "ui_gauge" | "仪表" | "ゲージ" | "게이지" | "มาตรวัด" | "گیج_رابط" | "مقياس_الواجهة" | "מד_ממשק" | "یو_آئی_گیج" | "jauge_ui" | "ui_anzeige" | "ui_индикатор" => {
10724 let cx = self.arg_num(&args, 0, 0.)? as f32;
10725 let cy = self.arg_num(&args, 1, 0.)? as f32;
10726 let r = self.arg_num(&args, 2, 50.)? as f32;
10727 let val = self.arg_num(&args, 3, 0.)? as f32;
10728 let max = self.arg_num(&args, 4, 1.)? as f32;
10729 let th = self.ui_theme;
10730 let needle = self.color_at(&args, 5, th.warn);
10731 self.draw_ui(&ling_ui::widgets::gauge(
10732 cx,
10733 cy,
10734 r,
10735 val / max.max(1e-6),
10736 needle,
10737 th.accent,
10738 th.track,
10739 ));
10740 return Ok(Value::Unit);
10741 },
10742 #[cfg(not(target_arch = "wasm32"))]
10743 "ui_ring" | "环表" | "リングメーター" | "링미터" | "วงแหวนวัด" | "حلقه_گیج" | "حلقة_قياس" | "טבעת_מד" | "رنگ_گیج" | "anneau_ui" | "ui_кольцо" =>
10744 {
10745 let cx = self.arg_num(&args, 0, 0.)? as f32;
10746 let cy = self.arg_num(&args, 1, 0.)? as f32;
10747 let r = self.arg_num(&args, 2, 40.)? as f32;
10748 let val = self.arg_num(&args, 3, 0.)? as f32;
10749 let max = self.arg_num(&args, 4, 1.)? as f32;
10750 let th = self.ui_theme;
10751 let fill = self.color_at(&args, 5, th.primary);
10752 self.draw_ui(&ling_ui::widgets::ring(
10753 cx,
10754 cy,
10755 r,
10756 val / max.max(1e-6),
10757 fill,
10758 th.track,
10759 ));
10760 return Ok(Value::Unit);
10761 },
10762 #[cfg(not(target_arch = "wasm32"))]
10763 "ui_vu" | "音量条" | "VUメーター" | "음량막대" | "มาตรเสียง" | "گیج_صدا" | "مقياس_مستوى_الصوت" | "מד_עוצמה" | "وی_یو_میٹر" | "vumètre_ui" | "ui_vumeter" | "ui_вю_метр" =>
10764 {
10765 let x = self.arg_num(&args, 0, 0.)? as f32;
10766 let y = self.arg_num(&args, 1, 0.)? as f32;
10767 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10768 let h0 = self.arg_num(&args, 3, 60.)? as f32;
10769 let levels = self.arg_list_f32(&args, 4);
10770 let th = self.ui_theme;
10771 let fill = self.color_at(&args, 5, th.primary);
10772 self.draw_ui(&ling_ui::widgets::vu(x, y, w0, h0, &levels, fill, th.warn));
10773 return Ok(Value::Unit);
10774 },
10775 #[cfg(not(target_arch = "wasm32"))]
10776 "ui_spark" | "迷你图" | "スパークライン" | "스파크라인" | "กราฟจิ๋ว" | "نمودار_ریز" | "رسم_مصغر" | "גרף_זעיר" | "اسپارک_لائن" | "mini_graphe_ui" | "ui_sparkline" | "ui_мини_график" =>
10777 {
10778 let x = self.arg_num(&args, 0, 0.)? as f32;
10779 let y = self.arg_num(&args, 1, 0.)? as f32;
10780 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10781 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10782 let vals = self.arg_list_f32(&args, 4);
10783 let th = self.ui_theme;
10784 let line = self.color_at(&args, 5, th.accent);
10785 self.draw_ui(&ling_ui::widgets::spark(x, y, w0, h0, &vals, line));
10786 return Ok(Value::Unit);
10787 },
10788 #[cfg(not(target_arch = "wasm32"))]
10789 "ui_battery" | "电池" | "バッテリー" | "배터리" | "แบตเตอรี่" | "نشانگر_باتری" | "مؤشر_البطارية" | "מחוון_סוללה" | "بیٹری_انڈیکیٹر" | "batterie_ui" | "ui_batterie" | "ui_батарея" =>
10790 {
10791 let x = self.arg_num(&args, 0, 0.)? as f32;
10792 let y = self.arg_num(&args, 1, 0.)? as f32;
10793 let w0 = self.arg_num(&args, 2, 50.)? as f32;
10794 let h0 = self.arg_num(&args, 3, 22.)? as f32;
10795 let val = self.arg_num(&args, 4, 1.)? as f32;
10796 let max = self.arg_num(&args, 5, 1.)? as f32;
10797 let th = self.ui_theme;
10798 let fill = self.color_at(&args, 6, th.accent);
10799 self.draw_ui(&ling_ui::widgets::battery(
10800 x,
10801 y,
10802 w0,
10803 h0,
10804 val / max.max(1e-6),
10805 fill,
10806 th.track,
10807 th.warn,
10808 ));
10809 return Ok(Value::Unit);
10810 },
10811
10812 #[cfg(not(target_arch = "wasm32"))]
10814 "ui_button" | "按钮" | "ボタン" | "버튼" | "ปุ่ม" | "دکمه_رابط" | "زر_الواجهة" | "כפתור_ממשק" | "یو_آئی_بٹن" | "bouton_ui" | "ui_knopf" | "ui_кнопка" => {
10815 let x = self.arg_num(&args, 0, 0.)? as f32;
10816 let y = self.arg_num(&args, 1, 0.)? as f32;
10817 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10818 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10819 let (mx, my, down) = self.mouse_now();
10820 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10821 let clicked = hover && down && !self.mouse_was_down;
10822 let th = self.ui_theme;
10823 let prim = self.color_at(&args, 4, th.primary);
10824 self.draw_ui(&ling_ui::widgets::button(
10825 x,
10826 y,
10827 w0,
10828 h0,
10829 hover,
10830 down && hover,
10831 prim,
10832 th.bg,
10833 ));
10834 return Ok(Value::Number(if clicked { 1.0 } else { 0.0 }));
10835 },
10836 #[cfg(not(target_arch = "wasm32"))]
10837 "ui_toggle" | "开关" | "トグル" | "토글" | "สวิตช์" | "کلید_ضامن" | "مفتاح_تبديل" | "מתג" | "ٹوگل" | "bascule_ui" | "ui_schalter" | "ui_переключатель" => {
10838 let x = self.arg_num(&args, 0, 0.)? as f32;
10839 let y = self.arg_num(&args, 1, 0.)? as f32;
10840 let w0 = self.arg_num(&args, 2, 52.)? as f32;
10841 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10842 let mut state = self.arg_num(&args, 4, 0.)? > 0.5;
10843 let (mx, my, down) = self.mouse_now();
10844 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10845 if hover && down && !self.mouse_was_down {
10846 state = !state;
10847 }
10848 let th = self.ui_theme;
10849 let on = self.color_at(&args, 5, th.accent);
10850 self.draw_ui(&ling_ui::widgets::toggle(x, y, w0, h0, state, on, th.track));
10851 return Ok(Value::Number(if state { 1.0 } else { 0.0 }));
10852 },
10853 #[cfg(not(target_arch = "wasm32"))]
10854 "ui_slider" | "滑块" | "スライダー" | "슬라이더" | "แถบเลื่อน" | "لغزنده" | "شريط_انزلاق" | "מחוון_החלקה" | "سلائیڈر" | "curseur_ui" | "ui_schieberegler" | "ui_ползунок" =>
10855 {
10856 let x = self.arg_num(&args, 0, 0.)? as f32;
10857 let y = self.arg_num(&args, 1, 0.)? as f32;
10858 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10859 let mut val = self.arg_num(&args, 3, 0.)? as f32;
10860 let mn = self.arg_num(&args, 4, 0.)? as f32;
10861 let mx_ = self.arg_num(&args, 5, 1.)? as f32;
10862 let (mx, my, down) = self.mouse_now();
10863 let hover = ling_ui::holo::hit_rect(mx, my, x - 8.0, y - 10.0, w0 + 16.0, 20.0);
10864 if hover && down {
10865 let frac = ((mx - x) / w0).clamp(0.0, 1.0);
10866 val = mn + (mx_ - mn) * frac;
10867 }
10868 let frac = ((val - mn) / (mx_ - mn).abs().max(1e-6)).clamp(0.0, 1.0);
10869 let th = self.ui_theme;
10870 let fill = self.color_at(&args, 6, th.primary);
10871 self.draw_ui(&ling_ui::widgets::slider(
10872 x, y, w0, frac, hover, fill, th.track,
10873 ));
10874 return Ok(Value::Number(val as f64));
10875 },
10876 #[cfg(not(target_arch = "wasm32"))]
10877 "ui_checkbox" | "复选框" | "チェックボックス" | "체크박스" | "ช่องเลือก" | "جعبه_علامت" | "مربع_اختيار" | "תיבת_סימון" | "چیک_باکس" | "case_cocher_ui" | "ui_kontrollkästchen" | "ui_флажок" =>
10878 {
10879 let x = self.arg_num(&args, 0, 0.)? as f32;
10880 let y = self.arg_num(&args, 1, 0.)? as f32;
10881 let s = self.arg_num(&args, 2, 20.)? as f32;
10882 let mut checked = self.arg_num(&args, 3, 0.)? > 0.5;
10883 let (mx, my, down) = self.mouse_now();
10884 let hover = ling_ui::holo::hit_rect(mx, my, x, y, s, s);
10885 if hover && down && !self.mouse_was_down {
10886 checked = !checked;
10887 }
10888 let th = self.ui_theme;
10889 let prim = self.color_at(&args, 4, th.primary);
10890 self.draw_ui(&ling_ui::widgets::checkbox(
10891 x, y, s, checked, hover, prim, th.track,
10892 ));
10893 return Ok(Value::Number(if checked { 1.0 } else { 0.0 }));
10894 },
10895 #[cfg(not(target_arch = "wasm32"))]
10896 "ui_tabs" | "标签页" | "タブ" | "탭" | "แท็บ" | "برگهها" | "ألسنة_الواجهة" | "לשוניות" | "ٹیبز" | "onglets_ui" | "ui_reiter" | "ui_вкладки" => {
10897 let x = self.arg_num(&args, 0, 0.)? as f32;
10898 let y = self.arg_num(&args, 1, 0.)? as f32;
10899 let w0 = self.arg_num(&args, 2, 240.)? as f32;
10900 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10901 let count = self.arg_num(&args, 4, 3.)? as usize;
10902 let mut active = self.arg_num(&args, 5, 0.)? as i32;
10903 let (mx, my, down) = self.mouse_now();
10904 let mut hover = -1;
10905 if my >= y && my <= y + h0 && mx >= x && mx <= x + w0 && count > 0 {
10906 hover = (((mx - x) / (w0 / count as f32)) as i32)
10907 .max(0)
10908 .min(count as i32 - 1);
10909 if down && !self.mouse_was_down {
10910 active = hover;
10911 }
10912 }
10913 let th = self.ui_theme;
10914 let prim = self.color_at(&args, 6, th.primary);
10915 self.draw_ui(&ling_ui::widgets::tabs(
10916 x,
10917 y,
10918 w0,
10919 h0,
10920 count,
10921 active as usize,
10922 hover,
10923 prim,
10924 th.track,
10925 ));
10926 return Ok(Value::Number(active as f64));
10927 },
10928 #[cfg(not(target_arch = "wasm32"))]
10929 "ui_progress" | "进度" | "プログレス" | "진행바" | "ความคืบหน้า" | "نوار_پیشرفت" | "شريط_التقدم" | "פס_התקדמות" | "پیش_رفت_بار" | "progression_ui" | "ui_fortschritt" | "ui_прогресс" =>
10930 {
10931 let x = self.arg_num(&args, 0, 0.)? as f32;
10932 let y = self.arg_num(&args, 1, 0.)? as f32;
10933 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10934 let h0 = self.arg_num(&args, 3, 12.)? as f32;
10935 let frac = self.arg_num(&args, 4, 0.)? as f32;
10936 let th = self.ui_theme;
10937 let fill = self.color_at(&args, 5, th.accent);
10938 self.draw_ui(&ling_ui::widgets::progress(
10939 x, y, w0, h0, frac, fill, th.track,
10940 ));
10941 return Ok(Value::Unit);
10942 },
10943 #[cfg(not(target_arch = "wasm32"))]
10944 "ui_tooltip" | "提示框" | "ツールチップ" | "툴팁" | "คำแนะนำ" | "راهنمای_شناور" | "تلميح_الواجهة" | "חלונית_עזרה" | "ٹول_ٹپ" | "infobulle_ui" | "ui_подсказка" =>
10945 {
10946 let x = self.arg_num(&args, 0, 0.)? as f32;
10947 let y = self.arg_num(&args, 1, 0.)? as f32;
10948 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10949 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10950 let th = self.ui_theme;
10951 let prim = self.color_at(&args, 4, th.primary);
10952 self.draw_ui(&ling_ui::widgets::tooltip(x, y, w0, h0, prim, th.bg));
10953 return Ok(Value::Unit);
10954 },
10955 #[cfg(not(target_arch = "wasm32"))]
10956 "ui_stepper" | "步进器" | "ステッパー" | "스테퍼" | "ตัวปรับค่า" | "پلهگر" | "زر_خطوات" | "בורר_מדורג" | "اسٹیپر" | "pas_à_pas_ui" | "ui_schrittsteuerung" | "ui_степпер" =>
10957 {
10958 let x = self.arg_num(&args, 0, 0.)? as f32;
10959 let y = self.arg_num(&args, 1, 0.)? as f32;
10960 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10961 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10962 let mut val = self.arg_num(&args, 4, 0.)? as f32;
10963 let step = self.arg_num(&args, 5, 1.)? as f32;
10964 let (mx, my, down) = self.mouse_now();
10965 let hm = ling_ui::holo::hit_rect(mx, my, x, y, h0, h0);
10966 let hp = ling_ui::holo::hit_rect(mx, my, x + w0 - h0, y, h0, h0);
10967 if down && !self.mouse_was_down {
10968 if hm {
10969 val -= step;
10970 }
10971 if hp {
10972 val += step;
10973 }
10974 }
10975 let th = self.ui_theme;
10976 let prim = self.color_at(&args, 6, th.primary);
10977 self.draw_ui(&ling_ui::widgets::stepper(
10978 x, y, w0, h0, hm, hp, prim, th.track,
10979 ));
10980 return Ok(Value::Number(val as f64));
10981 },
10982
10983 #[cfg(not(target_arch = "wasm32"))]
10985 "ui_healthbar" | "血条" | "体力バー" | "체력바" | "แถบพลังชีวิต" | "نوار_سلامتی" | "شريط_الصحة" | "פס_בריאות" | "ہیلتھ_بار" | "barre_vie_ui" | "ui_lebensbalken" | "ui_полоса_здоровья" =>
10986 {
10987 let x = self.arg_num(&args, 0, 0.)? as f32;
10988 let y = self.arg_num(&args, 1, 0.)? as f32;
10989 let w0 = self.arg_num(&args, 2, 180.)? as f32;
10990 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10991 let val = self.arg_num(&args, 4, 1.)? as f32;
10992 let max = self.arg_num(&args, 5, 1.)? as f32;
10993 let pulse = self.arg_num(&args, 6, 0.)? as f32;
10994 let th = self.ui_theme;
10995 let full = self.color_at(&args, 7, th.accent);
10996 self.draw_ui(&ling_ui::widgets::healthbar(
10997 x,
10998 y,
10999 w0,
11000 h0,
11001 val / max.max(1e-6),
11002 pulse,
11003 full,
11004 th.warn,
11005 th.track,
11006 ));
11007 return Ok(Value::Unit);
11008 },
11009 #[cfg(not(target_arch = "wasm32"))]
11010 "ui_cooldown" | "冷却" | "クールダウン" | "쿨다운" | "คูลดาวน์" | "زمان_خنکسازی" | "مؤقت_التهدئة" | "זמן_קירור" | "کول_ڈاؤن" | "recharge_ui" | "ui_abklingzeit" | "ui_перезарядка" =>
11011 {
11012 let cx = self.arg_num(&args, 0, 0.)? as f32;
11013 let cy = self.arg_num(&args, 1, 0.)? as f32;
11014 let r = self.arg_num(&args, 2, 28.)? as f32;
11015 let frac = self.arg_num(&args, 3, 0.)? as f32;
11016 let th = self.ui_theme;
11017 let fill = self.color_at(&args, 4, th.primary);
11018 self.draw_ui(&ling_ui::widgets::cooldown(cx, cy, r, frac, fill, th.track));
11019 return Ok(Value::Unit);
11020 },
11021 #[cfg(not(target_arch = "wasm32"))]
11022 "ui_counter" | "计数器" | "カウンター" | "카운터" | "ตัวนับ" | "شمارشگر" | "عداد_الواجهة" | "מונה_ממשק" | "کاؤنٹر" | "compteur_ui" | "ui_zähler" | "ui_счётчик" => {
11023 let x = self.arg_num(&args, 0, 0.)? as f32;
11024 let y = self.arg_num(&args, 1, 0.)? as f32;
11025 let dw = self.arg_num(&args, 2, 14.)? as f32;
11026 let dh = self.arg_num(&args, 3, 24.)? as f32;
11027 let val = self.arg_num(&args, 4, 0.)? as i64;
11028 let digits = self.arg_num(&args, 5, 4.)? as usize;
11029 let th = self.ui_theme;
11030 let on = self.color_at(&args, 6, th.primary);
11031 let off = ling_ui::widgets::shade(th.track, 0.5);
11032 self.draw_ui(&ling_ui::widgets::counter(
11033 x, y, dw, dh, val, digits, on, off,
11034 ));
11035 return Ok(Value::Unit);
11036 },
11037 #[cfg(not(target_arch = "wasm32"))]
11038 "ui_minimap" | "小地图" | "ミニマップ" | "미니맵" | "แผนที่ย่อ" | "نقشه_کوچک" | "خريطة_مصغرة" | "מפה_מוקטנת" | "منی_میپ" | "minicarte_ui" | "ui_minikarte" | "ui_миникарта" =>
11039 {
11040 let x = self.arg_num(&args, 0, 0.)? as f32;
11041 let y = self.arg_num(&args, 1, 0.)? as f32;
11042 let w0 = self.arg_num(&args, 2, 140.)? as f32;
11043 let h0 = self.arg_num(&args, 3, 140.)? as f32;
11044 let th = self.ui_theme;
11045 let prim = self.color_at(&args, 4, th.primary);
11046 self.draw_ui(&ling_ui::widgets::minimap(x, y, w0, h0, prim, th.bg));
11047 return Ok(Value::Unit);
11048 },
11049 #[cfg(not(target_arch = "wasm32"))]
11050 "ui_dpad" | "方向键" | "方向パッド" | "방향패드" | "ปุ่มทิศทาง" | "دسته_جهتدار" | "لوحة_الاتجاهات" | "לוח_כיוונים" | "ڈی_پیڈ" | "croix_direction_ui" | "ui_steuerkreuz" | "ui_крестовина" =>
11051 {
11052 let cx = self.arg_num(&args, 0, 0.)? as f32;
11053 let cy = self.arg_num(&args, 1, 0.)? as f32;
11054 let r = self.arg_num(&args, 2, 50.)? as f32;
11055 let (mx, my, down) = self.mouse_now();
11056 let mut dir = 0;
11057 if down {
11058 let (dx, dy) = (mx - cx, my - cy);
11059 if dx * dx + dy * dy <= r * r {
11060 if dx.abs() > dy.abs() {
11061 dir = if dx > 0.0 { 2 } else { 4 };
11062 } else {
11063 dir = if dy > 0.0 { 3 } else { 1 };
11064 }
11065 }
11066 }
11067 let th = self.ui_theme;
11068 let prim = self.color_at(&args, 3, th.primary);
11069 self.draw_ui(&ling_ui::widgets::dpad(cx, cy, r, dir, prim, th.track));
11070 return Ok(Value::Number(dir as f64));
11071 },
11072 #[cfg(not(target_arch = "wasm32"))]
11073 "ui_slotgrid" | "物品格" | "スロットグリッド" | "슬롯격자" | "ช่องไอเทม" | "شبکه_شیار" | "شبكة_الفتحات" | "רשת_חריצים" | "سلاٹ_گرڈ" | "grille_emplacements_ui" | "ui_slotraster" | "ui_сетка_слотов" =>
11074 {
11075 let x = self.arg_num(&args, 0, 0.)? as f32;
11076 let y = self.arg_num(&args, 1, 0.)? as f32;
11077 let cols = self.arg_num(&args, 2, 4.)? as usize;
11078 let rows = self.arg_num(&args, 3, 1.)? as usize;
11079 let cell = self.arg_num(&args, 4, 36.)? as f32;
11080 let sel = self.arg_num(&args, 5, -1.)? as i32;
11081 let th = self.ui_theme;
11082 let prim = self.color_at(&args, 6, th.primary);
11083 self.draw_ui(&ling_ui::widgets::slotgrid(
11084 x, y, cols, rows, cell, sel, prim, th.track,
11085 ));
11086 return Ok(Value::Unit);
11087 },
11088 #[cfg(not(target_arch = "wasm32"))]
11089 "ui_vignette" | "暗角" | "ビネット" | "비네트" | "ขอบมืด" | "سایهگرد_کادر" | "تظليل_الحواف" | "הצללת_מסגרת" | "ویگنیٹ" | "vignette_ui" | "ui_виньетка" => {
11090 let intensity = self.arg_num(&args, 0, 0.5)? as f32;
11091 let (w, h) = {
11092 let g = self.gfx.borrow();
11093 (g.width as f32, g.height as f32)
11094 };
11095 let th = self.ui_theme;
11096 let col = self.color_at(&args, 1, th.warn);
11097 self.draw_ui(&ling_ui::widgets::vignette(w, h, intensity, col));
11098 return Ok(Value::Unit);
11099 },
11100
11101 #[cfg(not(target_arch = "wasm32"))]
11103 "ui_gauge3d" | "立体仪表" | "立体ゲージ" | "입체게이지" | "มาตรวัด3มิติ" | "گیج_سهبعدی" | "مقياس_ثلاثي_الأبعاد" | "מד_תלת_ממדי" | "تھری_ڈی_گیج" | "jauge_3d_ui" | "ui_anzeige_3d" | "ui_индикатор_3d" =>
11104 {
11105 let cx = self.arg_num(&args, 0, 0.)? as f32;
11106 let cy = self.arg_num(&args, 1, 0.)? as f32;
11107 let r = self.arg_num(&args, 2, 50.)? as f32;
11108 let val = self.arg_num(&args, 3, 0.)? as f32;
11109 let max = self.arg_num(&args, 4, 1.)? as f32;
11110 let spin = self.arg_num(&args, 5, 0.)? as f32;
11111 let th = self.ui_theme;
11112 let fill = self.color_at(&args, 6, th.primary);
11113 self.draw_ui(&ling_ui::widgets::gauge3d(
11114 cx,
11115 cy,
11116 r,
11117 val / max.max(1e-6),
11118 spin,
11119 fill,
11120 th.track,
11121 ));
11122 return Ok(Value::Unit);
11123 },
11124 #[cfg(not(target_arch = "wasm32"))]
11125 "ui_panel3d" | "立体面板" | "立体パネル" | "입체패널" | "แผง3มิติ" | "پنل_سهبعدی" | "لوحة_ثلاثية_الأبعاد" | "לוח_תלת_ממדי" | "تھری_ڈی_پینل" | "panneau_3d_ui" | "ui_feld_3d" | "ui_панель_3d" =>
11126 {
11127 let x = self.arg_num(&args, 0, 0.)? as f32;
11128 let y = self.arg_num(&args, 1, 0.)? as f32;
11129 let w0 = self.arg_num(&args, 2, 200.)? as f32;
11130 let h0 = self.arg_num(&args, 3, 120.)? as f32;
11131 let depth = self.arg_num(&args, 4, 14.)? as f32;
11132 let th = self.ui_theme;
11133 let prim = self.color_at(&args, 5, th.primary);
11134 self.draw_ui(&ling_ui::widgets::panel3d(x, y, w0, h0, depth, prim, th.bg));
11135 return Ok(Value::Unit);
11136 },
11137 #[cfg(not(target_arch = "wasm32"))]
11138 "ui_radar3d" | "立体雷达" | "立体レーダー" | "입체레이더" | "เรดาร์3มิติ" | "رادار_سهبعدی" | "رادار_ثلاثي_الأبعاد" | "מכ״ם_תלת_ממדי" | "تھری_ڈی_ریڈار" | "radar_3d_ui" | "ui_radar_3d" | "ui_радар_3d" =>
11139 {
11140 let cx = self.arg_num(&args, 0, 0.)? as f32;
11141 let cy = self.arg_num(&args, 1, 0.)? as f32;
11142 let r = self.arg_num(&args, 2, 60.)? as f32;
11143 let tilt = self.arg_num(&args, 3, 0.9)? as f32;
11144 let sweep = self.arg_num(&args, 4, 0.)? as f32;
11145 let th = self.ui_theme;
11146 let prim = self.color_at(&args, 5, th.primary);
11147 self.draw_ui(&ling_ui::widgets::radar3d(
11148 cx, cy, r, tilt, sweep, prim, th.track,
11149 ));
11150 return Ok(Value::Unit);
11151 },
11152
11153 #[cfg(not(target_arch = "wasm32"))]
11155 "audio_blip" | "提示音" | "ビープ音" | "효과음" | "เสียงบี๊บ" | "بوق_کوتاه" | "نغمة_قصيرة" | "ביפ" | "بلپ_آواز" | "bip_audio" | "звук_бип" =>
11156 {
11157 let freq = self.arg_num(&args, 0, 660.)? as f32;
11158 let dur = self.arg_num(&args, 1, 0.08)? as f32;
11159 let wave = Wave::from_name(&self.arg_str(&args, 2, "sine"));
11160 let amp = self.arg_num(&args, 3, 0.25)? as f32;
11161 if let Some(audio) = &self.audio {
11162 audio.blip(freq, amp, dur, wave);
11163 }
11164 return Ok(Value::Unit);
11165 },
11166 #[cfg(not(target_arch = "wasm32"))]
11167 "ui_sound" | "界面音" | "UI音" | "인터페이스음" | "เสียงปุ่ม" | "صدای_رابط" | "صوت_الواجهة" | "צליל_ממשק" | "یو_آئی_آواز" | "son_ui" | "ui_klang" | "ui_звук" =>
11168 {
11169 let name = self.arg_str(&args, 0, "click");
11170 if let Some(audio) = &self.audio {
11171 match name.as_str() {
11172 "hover" => audio.blip(880.0, 0.10, 0.04, Wave::Sine),
11173 "confirm" => {
11174 audio.blip(660.0, 0.22, 0.07, Wave::Square);
11175 audio.blip(990.0, 0.18, 0.10, Wave::Square);
11176 },
11177 "error" => {
11178 audio.blip(180.0, 0.30, 0.16, Wave::Saw);
11179 audio.blip(140.0, 0.30, 0.18, Wave::Saw);
11180 },
11181 "toggle" => audio.blip(520.0, 0.22, 0.05, Wave::Triangle),
11182 "tick" => audio.blip(1500.0, 0.12, 0.02, Wave::Square),
11183 _ => audio.blip(720.0, 0.26, 0.05, Wave::Square), }
11185 }
11186 return Ok(Value::Unit);
11187 },
11188
11189 #[cfg(not(target_arch = "wasm32"))]
11197 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" | "charger_musique" | "musik_laden" | "загрузить_музыку" =>
11198 {
11199 let path = self.arg_str(&args, 0, "");
11200 let resolved = if std::path::Path::new(&path).exists() {
11201 path.clone()
11202 } else if let Some(d) = &self.source_dir {
11203 d.join(&path).to_string_lossy().into_owned()
11204 } else {
11205 path.clone()
11206 };
11207 match ling_music::load(&resolved) {
11208 Ok(t) => {
11209 let id = self.tracks.len();
11210 self.tracks.push(t);
11211 return Ok(Value::Number(id as f64));
11212 },
11213 Err(e) => {
11214 eprintln!("music_load failed ({path}): {e}");
11215 return Ok(Value::Number(-1.0));
11216 },
11217 }
11218 },
11219 #[cfg(not(target_arch = "wasm32"))]
11220 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" | "durée_musique" | "musik_dauer" | "длительность_музыки" =>
11221 {
11222 let id = self.arg_num(&args, 0, 0.0)? as i64;
11223 let d = self
11224 .tracks
11225 .get(id as usize)
11226 .map(|t| t.duration)
11227 .unwrap_or(0.0);
11228 return Ok(Value::Number(d as f64));
11229 },
11230 #[cfg(not(target_arch = "wasm32"))]
11231 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" | "ضربان_در_دقیقه" | "نبضات_بالدقيقة" | "פעימות_לדקה" | "بی_پی_ایم" | "bpm_musique" | "musik_bpm" | "музыка_bpm" =>
11232 {
11233 let id = self.arg_num(&args, 0, 0.0)? as i64;
11234 let b = self
11235 .tracks
11236 .get(id as usize)
11237 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
11238 .unwrap_or(0.0);
11239 return Ok(Value::Number(b as f64));
11240 },
11241 #[cfg(not(target_arch = "wasm32"))]
11242 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" | "tonalité_musique" | "musik_tonart" | "тональность_музыки" => {
11243 let id = self.arg_num(&args, 0, 0.0)? as i64;
11244 let k = self
11245 .tracks
11246 .get(id as usize)
11247 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
11248 .unwrap_or_default();
11249 return Ok(Value::Str(k));
11250 },
11251 #[cfg(not(target_arch = "wasm32"))]
11252 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" | "attaques_musique" | "musik_einsätze" | "атаки_музыки" =>
11253 {
11254 let id = self.arg_num(&args, 0, 0.0)? as i64;
11255 let v = self
11256 .tracks
11257 .get(id as usize)
11258 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
11259 .unwrap_or_default();
11260 return Ok(Value::List(Rc::new(
11261 v.into_iter().map(|x| Value::Number(x as f64)).collect(),
11262 )));
11263 },
11264 #[cfg(not(target_arch = "wasm32"))]
11265 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" | "شبکه_ضرب" | "شبكة_الإيقاع" | "רשת_פעימות" | "بیٹ_گرڈ" | "grille_temps_musique" | "musik_taktraster" | "сетка_ритма_музыки" =>
11266 {
11267 let id = self.arg_num(&args, 0, 0.0)? as i64;
11268 let beats = self
11269 .tracks
11270 .get(id as usize)
11271 .map(|t| {
11272 let b = ling_music::analysis::bpm(&t.mono, t.rate);
11273 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
11274 })
11275 .unwrap_or_default();
11276 return Ok(Value::List(Rc::new(
11277 beats.into_iter().map(|x| Value::Number(x as f64)).collect(),
11278 )));
11279 },
11280
11281 #[cfg(not(target_arch = "wasm32"))]
11283 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" | "jouer_musique" | "musik_abspielen" | "играть_музыку" =>
11284 {
11285 let id = self.arg_num(&args, 0, 0.0)? as i64;
11286 if self.ensure_music() {
11287 let track = self
11288 .tracks
11289 .get(id as usize)
11290 .map(|t| (t.stereo.clone(), t.rate));
11291 if let (Some((st, rate)), Some(m)) = (track, &self.music) {
11292 m.set_track(st, rate);
11293 m.play();
11294 } else if let Some(m) = &self.music {
11295 m.play();
11296 }
11297 }
11298 return Ok(Value::Unit);
11299 },
11300 #[cfg(not(target_arch = "wasm32"))]
11301 "music_pause" | "暂停音乐" | "音楽一時停止" | "음악일시정지" | "หยุดเพลงชั่วคราว" | "مکث_موسیقی" | "ألبث_الموسيقى" | "השהה_מוזיקה" | "موسیقی_روکو_مؤقت" | "pause_musique" | "musik_pausieren" | "пауза_музыки" =>
11302 {
11303 if let Some(m) = &self.music {
11304 m.pause();
11305 }
11306 return Ok(Value::Unit);
11307 },
11308 #[cfg(not(target_arch = "wasm32"))]
11309 "music_stop" | "停止音乐" | "音楽停止" | "음악정지" | "หยุดเพลง" | "توقف_موسیقی" | "أوقف_الموسيقى" | "עצור_מוזיקה" | "موسیقی_روکو" | "arrêter_musique" | "musik_stoppen" | "остановить_музыку" =>
11310 {
11311 if let Some(m) = &self.music {
11312 m.stop();
11313 }
11314 return Ok(Value::Unit);
11315 },
11316 #[cfg(not(target_arch = "wasm32"))]
11317 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" | "جستجوی_موسیقی" | "ابحث_في_الموسيقى" | "חפש_במוזיקה" | "موسیقی_تلاش" | "chercher_musique" | "musik_suchen" | "перемотать_музыку" =>
11318 {
11319 let sec = self.arg_num(&args, 0, 0.0)? as f32;
11320 if let Some(m) = &self.music {
11321 m.seek(sec);
11322 }
11323 return Ok(Value::Unit);
11324 },
11325 #[cfg(not(target_arch = "wasm32"))]
11326 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" | "موقعیت_موسیقی" | "موضع_الموسيقى" | "מיקום_מוזיקה" | "موسیقی_مقام" | "position_musique" | "musik_position" | "позиция_музыки" =>
11327 {
11328 let p = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
11329 return Ok(Value::Number(p as f64));
11330 },
11331 #[cfg(not(target_arch = "wasm32"))]
11332 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" | "بلندی_موسیقی" | "مستوى_الموسيقى" | "עוצמת_מוזיקה" | "موسیقی_شدت" | "volume_musique" | "musik_lautstärke" | "громкость_музыки" =>
11333 {
11334 let v = self.arg_num(&args, 0, 0.8)? as f32;
11335 if self.ensure_music() {
11336 if let Some(m) = &self.music {
11337 m.set_volume(v);
11338 }
11339 }
11340 return Ok(Value::Unit);
11341 },
11342
11343 #[cfg(not(target_arch = "wasm32"))]
11345 "music_patch" | "乐器音色" | "音色読込" | "악기패치" | "แพตช์เครื่องดนตรี" | "پچ_موسیقی" | "آلة_الموسيقى" | "תיקון_כלי_נגינה" | "میوزک_پیچ" | "patch_musique" | "musik_patch" | "патч_музыки" =>
11346 {
11347 let path = self.arg_str(&args, 0, "");
11348 let resolved = if std::path::Path::new(&path).exists() {
11349 path.clone()
11350 } else if let Some(d) = &self.source_dir {
11351 d.join(&path).to_string_lossy().into_owned()
11352 } else {
11353 path.clone()
11354 };
11355 if !self.ensure_music() {
11356 return Ok(Value::Number(-1.0));
11357 }
11358 match ling_music::patch::from_path(&resolved) {
11359 Ok(p) => {
11360 let id = self.music.as_ref().unwrap().add_patch(p);
11361 return Ok(Value::Number(id as f64));
11362 },
11363 Err(e) => {
11364 eprintln!("music_patch failed ({path}): {e}");
11365 return Ok(Value::Number(-1.0));
11366 },
11367 }
11368 },
11369 #[cfg(not(target_arch = "wasm32"))]
11370 "music_note" | "弹音符" | "音符演奏" | "음표연주" | "เล่นโน้ต" | "نواختن_نت" | "عزف_نغمة" | "נגן_תו" | "نوٹ_بجاؤ" | "note_musique" | "musik_note" | "нота_музыки" =>
11371 {
11372 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11373 let midi = self.pitch_arg(&args, 1, 60);
11374 let dur = self.arg_num(&args, 2, 0.5)? as f32;
11375 let vel = self.arg_num(&args, 3, 0.9)? as f32;
11376 if self.ensure_music() {
11377 if let Some(m) = &self.music {
11378 m.note(inst, midi, vel, dur);
11379 }
11380 }
11381 return Ok(Value::Unit);
11382 },
11383 #[cfg(not(target_arch = "wasm32"))]
11384 "music_note_on" | "音符开始" | "音符オン" | "음표켜기" | "โน้ตเริ่ม" | "شروع_نت" | "بدء_النغمة" | "התחלת_תו" | "نوٹ_شروع" | "note_musique_on" | "musik_note_an" | "нота_музыки_вкл" =>
11385 {
11386 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11387 let midi = self.pitch_arg(&args, 1, 60);
11388 let vel = self.arg_num(&args, 2, 0.9)? as f32;
11389 if self.ensure_music() {
11390 if let Some(m) = &self.music {
11391 m.note_on(inst, midi, vel);
11392 }
11393 }
11394 return Ok(Value::Unit);
11395 },
11396 #[cfg(not(target_arch = "wasm32"))]
11397 "music_note_off" | "音符结束" | "音符オフ" | "음표끄기" | "โน้ตจบ" | "پایان_نت" | "إيقاف_النغمة" | "סיום_תו" | "نوٹ_ختم" | "note_musique_off" | "musik_note_aus" | "нота_музыки_выкл" =>
11398 {
11399 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11400 let midi = self.pitch_arg(&args, 1, 60);
11401 if let Some(m) = &self.music {
11402 m.note_off(inst, midi);
11403 }
11404 return Ok(Value::Unit);
11405 },
11406
11407 #[cfg(not(target_arch = "wasm32"))]
11409 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" | "داوری_ضرب" | "حكم_الإيقاع" | "שיפוט_קצב" | "بیٹ_فیصلہ" | "juger_musique" | "musik_bewerten" | "оценить_музыку" =>
11410 {
11411 let delta_ms = self.arg_num(&args, 0, 9999.0)? as f32;
11412 return Ok(Value::Number(
11413 ling_music::Grade::judge(delta_ms).index() as f64
11414 ));
11415 },
11416 #[cfg(not(target_arch = "wasm32"))]
11417 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" | "نام_رتبه" | "اسم_التقييم" | "שם_דירוג" | "گریڈ_نام" | "nom_grade_musique" | "musik_bewertungsname" | "имя_оценки_музыки" =>
11418 {
11419 let idx = self.arg_num(&args, 0, 4.0)? as i32;
11420 return Ok(Value::Str(
11421 ling_music::Grade::from_index(idx).name().to_string(),
11422 ));
11423 },
11424
11425 #[cfg(not(target_arch = "wasm32"))]
11427 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" | "بارگذاری_متن_ترانه" | "تحميل_كلمات_الأغنية" | "טעינת_מילות_שיר" | "گیت_متن_لوڈ" | "lrc_musique" | "musik_lrc" | "lrc_музыки" =>
11428 {
11429 let path = self.arg_str(&args, 0, "");
11430 let resolved = if std::path::Path::new(&path).exists() {
11431 path.clone()
11432 } else if let Some(d) = &self.source_dir {
11433 d.join(&path).to_string_lossy().into_owned()
11434 } else {
11435 path.clone()
11436 };
11437 match std::fs::read_to_string(&resolved) {
11438 Ok(text) => {
11439 let id = self.lyrics.len();
11440 self.lyrics.push(ling_music::Lyrics::parse(&text));
11441 return Ok(Value::Number(id as f64));
11442 },
11443 Err(e) => {
11444 eprintln!("music_lrc failed ({path}): {e}");
11445 return Ok(Value::Number(-1.0));
11446 },
11447 }
11448 },
11449 #[cfg(not(target_arch = "wasm32"))]
11450 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" | "متن_ترانه_فعلی" | "كلمات_الأغنية_الحالية" | "מילות_שיר_נוכחיות" | "موجودہ_گیت_متن" | "paroles_musique" | "musik_liedtext" | "текст_песни" =>
11451 {
11452 let id = self.arg_num(&args, 0, 0.0)? as i64;
11453 let t = self.arg_num(&args, 1, 0.0)? as f32;
11454 let line = self
11455 .lyrics
11456 .get(id as usize)
11457 .map(|l| l.line_at(t).to_string())
11458 .unwrap_or_default();
11459 return Ok(Value::Str(line));
11460 },
11461 #[cfg(not(target_arch = "wasm32"))]
11462 "music_mic_pitch" | "麦克风音高" | "マイク音程" | "마이크음정" | "ระดับเสียงไมค์" | "زیروبمی_میکروفون" | "طبقة_صوت_الميكروفون" | "גובה_צליל_מיקרופון" | "مائیکروفون_پچ" | "hauteur_micro_musique" | "musik_mikrofon_tonhöhe" | "высота_тона_микрофона" =>
11463 {
11464 let hz = if let Some(mic) = self.mic.as_ref() {
11465 let s = mic.latest_samples();
11466 let rate = mic.sample_rate();
11467 ling_music::pitch::detect(&s, rate).unwrap_or(0.0)
11468 } else {
11469 0.0
11470 };
11471 return Ok(Value::Number(hz as f64));
11472 },
11473 #[cfg(not(target_arch = "wasm32"))]
11474 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" | "نام_نت" | "اسم_النغمة" | "שם_תו" | "نوٹ_نام" | "nom_note_musique" | "musik_notenname" | "имя_ноты_музыки" =>
11475 {
11476 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11477 return Ok(Value::Str(ling_music::note::hz_to_name(hz)));
11478 },
11479 #[cfg(not(target_arch = "wasm32"))]
11480 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" | "فرکانس_نت" | "تردد_النغمة" | "תדר_תו" | "نوٹ_ہرٹز" | "hz_musique" | "musik_hz" | "музыка_гц" =>
11481 {
11482 let midi = self.pitch_arg(&args, 0, 69);
11483 return Ok(Value::Number(
11484 ling_music::note::midi_to_hz(midi as f32) as f64
11485 ));
11486 },
11487 #[cfg(not(target_arch = "wasm32"))]
11488 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" | "امتیاز_زیروبمی" | "درجة_طبقة_الصوت" | "ציון_גובה_צליל" | "پچ_اسکور" | "score_hauteur_musique" | "musik_tonhöhen_punktzahl" | "счёт_высоты_тона" =>
11489 {
11490 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11491 let target = self.arg_num(&args, 1, 0.0)? as f32;
11492 return Ok(Value::Number(
11493 ling_music::karaoke::pitch_score(hz, target) as f64
11494 ));
11495 },
11496
11497 #[cfg(not(target_arch = "wasm32"))]
11499 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" | "بارگذاری_MIDI" | "تحميل_MIDI" | "טעינת_MIDI" | "MIDI_لوڈ" | "charger_midi_musique" | "musik_midi_laden" | "загрузить_midi_музыки" =>
11500 {
11501 let path = self.arg_str(&args, 0, "");
11502 let resolved = if std::path::Path::new(&path).exists() {
11503 path.clone()
11504 } else if let Some(d) = &self.source_dir {
11505 d.join(&path).to_string_lossy().into_owned()
11506 } else {
11507 path.clone()
11508 };
11509 match ling_music::midi::load(&resolved) {
11510 Ok(m) => {
11511 let id = self.midis.len();
11512 self.midis.push(m);
11513 return Ok(Value::Number(id as f64));
11514 },
11515 Err(e) => {
11516 eprintln!("music_midi_load failed ({path}): {e}");
11517 return Ok(Value::Number(-1.0));
11518 },
11519 }
11520 },
11521 #[cfg(not(target_arch = "wasm32"))]
11522 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" | "تعداد_MIDI" | "عدد_MIDI" | "מספר_MIDI" | "MIDI_تعداد" | "nombre_midi_musique" | "musik_midi_anzahl" | "число_midi_музыки" =>
11523 {
11524 let id = self.arg_num(&args, 0, 0.0)? as i64;
11525 let n = self
11526 .midis
11527 .get(id as usize)
11528 .map(|m| m.notes.len())
11529 .unwrap_or(0);
11530 return Ok(Value::Number(n as f64));
11531 },
11532 #[cfg(not(target_arch = "wasm32"))]
11534 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" | "نتهای_MIDI" | "نغمات_MIDI" | "תווי_MIDI" | "MIDI_نوٹس" | "notes_midi_musique" | "musik_midi_noten" | "ноты_midi_музыки" =>
11535 {
11536 let id = self.arg_num(&args, 0, 0.0)? as i64;
11537 let mut out = Vec::new();
11538 if let Some(m) = self.midis.get(id as usize) {
11539 for n in &m.notes {
11540 out.push(Value::Number(n.time as f64));
11541 out.push(Value::Number(n.midi as f64));
11542 }
11543 }
11544 return Ok(Value::List(Rc::new(out)));
11545 },
11546 #[cfg(not(target_arch = "wasm32"))]
11548 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" | "میلههای_MIDI" | "أعمدة_MIDI" | "עמודות_MIDI" | "MIDI_بارز" | "mesures_midi_musique" | "musik_midi_takte" | "такты_midi_музыки" =>
11549 {
11550 let id = self.arg_num(&args, 0, 0.0)? as i64;
11551 let mut out = Vec::new();
11552 if let Some(m) = self.midis.get(id as usize) {
11553 for n in &m.notes {
11554 out.push(Value::Number(n.time as f64));
11555 out.push(Value::Number(n.midi as f64));
11556 out.push(Value::Number(n.dur as f64));
11557 }
11558 }
11559 return Ok(Value::List(Rc::new(out)));
11560 },
11561
11562 #[cfg(not(target_arch = "wasm32"))]
11564 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" | "طیف_موسیقی" | "طيف_الموسيقى" | "ספקטרום_מוזיקה" | "میوزک_اسپیکٹرم" | "fft_musique" | "musik_fft" | "fft_музыки" =>
11565 {
11566 let id = self.arg_num(&args, 0, 0.0)? as i64;
11567 let nbands = self.arg_num(&args, 1, 16.0)? as usize;
11568 let pos = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
11569 if let Some(t) = self.tracks.get(id as usize) {
11570 let idx = (pos * t.rate as f32) as usize;
11571 let end = (idx + 2048).min(t.mono.len());
11572 if end > idx + 64 {
11573 self.fft.borrow_mut().push_samples(&t.mono[idx..end]);
11574 }
11575 }
11576 let bands = self.fft.borrow().freq_bands(nbands);
11577 return Ok(Value::List(Rc::new(
11578 bands.into_iter().map(|x| Value::Number(x as f64)).collect(),
11579 )));
11580 },
11581
11582 #[cfg(not(target_arch = "wasm32"))]
11584 "audio_stop_sfx" | "停止音效" | "効果音停止" | "효과음정지" | "หยุดเอฟเฟกต์ทั้งหมด" | "توقف_همه_جلوهها" | "أوقف_كل_المؤثرات" | "עצור_כל_האפקטים" | "تمام_ایفیکٹ_روکو" =>
11585 {
11586 if let Some(a) = &self.audio {
11587 a.stop_all_sfx();
11588 }
11589 return Ok(Value::Unit);
11590 },
11591 #[cfg(not(target_arch = "wasm32"))]
11593 "audio_sfx" | "音效" | "空間効果音" | "공간효과음" | "เสียงเอฟเฟกต์" | "جلوه_صوتی" | "مؤثرات_صوتية" | "אפקט_קול" | "آواز_ایفیکٹ" | "effet_sonore" | "klangeffekt" | "звуковой_эффект" =>
11594 {
11595 let x = self.arg_num(&args, 0, 0.0)? as f32;
11596 let y = self.arg_num(&args, 1, 0.0)? as f32;
11597 let z = self.arg_num(&args, 2, 0.0)? as f32;
11598 let w = self.arg_num(&args, 3, 1.0)? as f32;
11599 let freq = self.arg_num(&args, 4, 440.0)? as f32;
11600 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11601 let dur = self.arg_num(&args, 6, 0.15)? as f32;
11602 let wave = Wave::from_name(&self.arg_str(&args, 7, "sine"));
11603 if let Some(a) = &self.audio {
11604 a.sfx(x, y, z, w, freq, amp, dur, wave);
11605 }
11606 return Ok(Value::Unit);
11607 },
11608 #[cfg(not(target_arch = "wasm32"))]
11613 "morph_note" | "โน้ตมอร์ฟ" | "变形音" | "モーフ音" | "모프음" | "نت_مورف" | "نغمة_متحولة" | "תו_מורף" | "مورف_نوٹ" =>
11614 {
11615 let x = self.arg_num(&args, 0, 0.0)? as f32;
11616 let y = self.arg_num(&args, 1, 0.0)? as f32;
11617 let z = self.arg_num(&args, 2, 0.0)? as f32;
11618 let w = self.arg_num(&args, 3, 1.0)? as f32;
11619 let freq = self.arg_num(&args, 4, 220.0)? as f32;
11620 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11621 let dur = self.arg_num(&args, 6, 0.6)? as f32;
11622 let material = self.arg_num(&args, 7, 0.0)?.clamp(0.0, 3.0) as u8;
11623 let morph = self.arg_num(&args, 8, 0.0)? as f32;
11624 if let Some(a) = &self.audio {
11625 a.morph_note(x, y, z, w, freq, amp, dur, material, morph);
11626 }
11627 return Ok(Value::Unit);
11628 },
11629 #[cfg(not(target_arch = "wasm32"))]
11631 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" | "بارگذاری_نمونه_صدا" | "تحميل_عينة_صوتية" | "טעינת_דגימת_קול" | "آواز_نمونہ_لوڈ" | "charger_échantillon" | "sample_laden" | "загрузить_семпл" =>
11632 {
11633 let path = self.arg_str(&args, 0, "");
11634 let resolved = if std::path::Path::new(&path).exists() {
11635 path.clone()
11636 } else if let Some(d) = &self.source_dir {
11637 d.join(&path).to_string_lossy().into_owned()
11638 } else {
11639 path.clone()
11640 };
11641 match ling_music::load(&resolved) {
11642 Ok(t) => {
11643 if let Some(a) = &self.audio {
11644 return Ok(Value::Number(a.add_sample(t.mono, t.rate) as f64));
11645 }
11646 return Ok(Value::Number(-1.0));
11647 },
11648 Err(e) => {
11649 eprintln!("audio_sample_load failed ({path}): {e}");
11650 return Ok(Value::Number(-1.0));
11651 },
11652 }
11653 },
11654 #[cfg(not(target_arch = "wasm32"))]
11655 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" | "پخش_نمونه_صدا" | "تشغيل_عينة_صوتية" | "נגינת_דגימת_קול" | "آواز_نمونہ_چلاؤ" | "jouer_échantillon" | "sample_abspielen" | "играть_семпл" =>
11656 {
11657 let id = self.arg_num(&args, 0, 0.0)? as usize;
11658 let x = self.arg_num(&args, 1, 0.0)? as f32;
11659 let y = self.arg_num(&args, 2, 0.0)? as f32;
11660 let z = self.arg_num(&args, 3, 0.0)? as f32;
11661 let w = self.arg_num(&args, 4, 1.0)? as f32;
11662 let vol = self.arg_num(&args, 5, 1.0)? as f32;
11663 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
11664 let v = self
11665 .audio
11666 .as_ref()
11667 .map(|a| a.play_sample(id, x, y, z, w, vol, looping))
11668 .unwrap_or(0);
11669 return Ok(Value::Number(v as f64));
11670 },
11671 #[cfg(not(target_arch = "wasm32"))]
11676 "audio_sample_play_flat" | "播放采样平" | "サンプル再生平" | "샘플재생평" | "เล่นตัวอย่างเสียงเรียบ" =>
11677 {
11678 let id = self.arg_num(&args, 0, 0.0)? as usize;
11679 let vol = self.arg_num(&args, 1, 1.0)? as f32;
11680 let looping = self.arg_num(&args, 2, 0.0)? > 0.5;
11681 let v = self
11682 .audio
11683 .as_ref()
11684 .map(|a| a.play_sample_flat(id, vol, looping))
11685 .unwrap_or(0);
11686 return Ok(Value::Number(v as f64));
11687 },
11688 #[cfg(not(target_arch = "wasm32"))]
11689 "audio_sample_stop" | "停止采样" | "サンプル停止" | "샘플정지" | "หยุดตัวอย่างเสียง" | "توقف_نمونه_صدا" | "إيقاف_عينة_صوتية" | "עצירת_דגימת_קול" | "آواز_نمونہ_روکو" | "arrêter_échantillon" | "sample_stoppen" | "остановить_семпл" =>
11690 {
11691 let v = self.arg_num(&args, 0, 0.0)? as u32;
11692 if let Some(a) = &self.audio {
11693 a.stop_sample(v);
11694 }
11695 return Ok(Value::Unit);
11696 },
11697 #[cfg(not(target_arch = "wasm32"))]
11702 "audio_sample_duration" | "采样时长" | "サンプル長さ" | "샘플길이" | "ความยาวตัวอย่างเสียง" =>
11703 {
11704 let id = self.arg_num(&args, 0, 0.0)? as usize;
11705 let dur = self.audio.as_ref().map(|a| a.sample_duration(id)).unwrap_or(0.0);
11706 return Ok(Value::Number(dur as f64));
11707 },
11708 #[cfg(not(target_arch = "wasm32"))]
11710 "audio_fx_delay" | "回声" | "ディレイ効果" | "딜레이" | "เสียงสะท้อน" | "افکت_تاخیر" | "صدى_تأخير" | "אפקט_עיכוב" | "تاخیر_ایفیکٹ" | "délai_audio" | "audio_verzögerung" | "звук_задержка" =>
11711 {
11712 let time = self.arg_num(&args, 0, 0.3)? as f32;
11713 let fb = self.arg_num(&args, 1, 0.3)? as f32;
11714 let mix = self.arg_num(&args, 2, 0.3)? as f32;
11715 if let Some(a) = &self.audio {
11716 a.fx_delay(time, fb, mix);
11717 }
11718 return Ok(Value::Unit);
11719 },
11720 #[cfg(not(target_arch = "wasm32"))]
11721 "audio_fx_reverb" | "混响" | "リバーブ" | "리버브" | "เสียงก้อง" | "افکت_پژواک" | "صدى_ارتداد" | "אפקט_הדהוד" | "بازگشت_آواز_ایفیکٹ" | "réverbération_audio" | "audio_nachhall" | "звук_реверберация" =>
11722 {
11723 let mix = self.arg_num(&args, 0, 0.3)? as f32;
11724 if let Some(a) = &self.audio {
11725 a.fx_reverb(mix);
11726 }
11727 return Ok(Value::Unit);
11728 },
11729 #[cfg(not(target_arch = "wasm32"))]
11730 "audio_fx_lowpass" | "低通滤波" | "ローパス" | "저역통과" | "กรองความถี่ต่ำ" | "فیلتر_پایینگذر" | "مرشح_تمرير_منخفض" | "מסנן_תדר_נמוך" | "لو_پاس_فلٹر" | "passe_bas_audio" | "audio_tiefpass" | "звук_фнч" =>
11731 {
11732 let cutoff = self.arg_num(&args, 0, 1.0)? as f32;
11733 if let Some(a) = &self.audio {
11734 a.fx_lowpass(cutoff);
11735 }
11736 return Ok(Value::Unit);
11737 },
11738
11739 #[cfg(not(target_arch = "wasm32"))]
11746 "soft_ball" | "软球" | "ソフトボール" | "소프트볼" | "ลูกบอลนุ่ม" | "توپ_نرم" | "كرة_ناعمة" | "כדור_רך" | "نرم_گیند" | "balle_molle" | "weicher_ball" | "мягкий_шар" =>
11747 {
11748 let x = self.arg_num(&args, 0, 0.)? as f32;
11749 let y = self.arg_num(&args, 1, 0.)? as f32;
11750 let z = self.arg_num(&args, 2, 0.)? as f32;
11751 let r = self.arg_num(&args, 3, 1.0)? as f32;
11752 let b = ling_physics::soft::SoftBody::sphere(
11753 ling_physics::Vec3::new(x, y, z),
11754 r,
11755 8,
11756 12,
11757 1.0,
11758 );
11759 let id = self.soft_bodies.len();
11760 self.soft_bodies.push(b);
11761 return Ok(Value::Number(id as f64));
11762 },
11763 #[cfg(not(target_arch = "wasm32"))]
11764 "soft_step" | "软体步进" | "ソフト更新" | "소프트스텝" | "ก้าวนุ่ม" | "گام_نرم_جسم" | "خطوة_ناعمة" | "צעד_רך" | "نرم_قدم" | "pas_mou" | "weicher_schritt" | "мягкий_шаг" =>
11765 {
11766 let id = self.arg_num(&args, 0, 0.)? as usize;
11767 let dt = self.arg_num(&args, 1, 0.016)? as f32;
11768 let gy = self.arg_num(&args, 2, 15.0)? as f32;
11769 if let Some(b) = self.soft_bodies.get_mut(id) {
11770 b.integrate(dt, ling_physics::Vec3::new(0.0, gy, 0.0), 4);
11771 }
11772 return Ok(Value::Unit);
11773 },
11774 #[cfg(not(target_arch = "wasm32"))]
11775 "soft_bounce" | "软体落地" | "ソフト着地" | "소프트바운스" | "เด้งนุ่ม" | "جهش_نرم" | "ارتداد_ناعم" | "קפיצה_רכה" | "نرم_اچھال" | "rebond_mou" | "weicher_abprall" | "мягкий_отскок" =>
11776 {
11777 let id = self.arg_num(&args, 0, 0.)? as usize;
11778 let fy = self.arg_num(&args, 1, 0.)? as f32;
11779 let rest = self.arg_num(&args, 2, 0.5)? as f32;
11780 if let Some(b) = self.soft_bodies.get_mut(id) {
11781 b.floor_collision(fy, rest);
11782 }
11783 return Ok(Value::Unit);
11784 },
11785 #[cfg(not(target_arch = "wasm32"))]
11786 "soft_contain" | "软体边界" | "ソフト箱" | "소프트경계" | "กล่องนุ่ม" | "محفظه_نرم" | "احتواء_ناعم" | "הכלה_רכה" | "نرم_احاطہ" | "contenir_mou" | "weiche_eindämmung" | "мягкое_сдерживание" =>
11787 {
11788 let id = self.arg_num(&args, 0, 0.)? as usize;
11789 let nx = self.arg_num(&args, 1, -5.)? as f32;
11790 let ny = self.arg_num(&args, 2, -5.)? as f32;
11791 let nz = self.arg_num(&args, 3, -5.)? as f32;
11792 let mx = self.arg_num(&args, 4, 5.)? as f32;
11793 let my = self.arg_num(&args, 5, 5.)? as f32;
11794 let mz = self.arg_num(&args, 6, 5.)? as f32;
11795 let rest = self.arg_num(&args, 7, 0.6)? as f32;
11796 if let Some(b) = self.soft_bodies.get_mut(id) {
11797 b.contain(
11798 ling_physics::Vec3::new(nx, ny, nz),
11799 ling_physics::Vec3::new(mx, my, mz),
11800 rest,
11801 );
11802 }
11803 return Ok(Value::Unit);
11804 },
11805 #[cfg(not(target_arch = "wasm32"))]
11806 "soft_kick" | "软体踢" | "ソフト衝撃" | "소프트킥" | "เตะนุ่ม" | "ضربه_نرم" | "ركلة_ناعمة" | "בעיטה_רכה" | "نرم_ٹھوکر" | "coup_mou" | "weicher_stoß" | "мягкий_удар" =>
11807 {
11808 let id = self.arg_num(&args, 0, 0.)? as usize;
11809 let dx = self.arg_num(&args, 1, 0.)? as f32;
11810 let dy = self.arg_num(&args, 2, 0.)? as f32;
11811 let dz = self.arg_num(&args, 3, 0.)? as f32;
11812 let s = self.arg_num(&args, 4, 0.1)? as f32;
11813 if let Some(b) = self.soft_bodies.get_mut(id) {
11814 b.kick(ling_physics::Vec3::new(dx, dy, dz), s);
11815 }
11816 return Ok(Value::Unit);
11817 },
11818 #[cfg(not(target_arch = "wasm32"))]
11821 "soft_spin" | "软体自旋" | "ソフト回転" | "소프트회전" | "หมุนนุ่ม" | "چرخش_نرم" | "دوران_ناعم" | "סיבוב_רך" | "نرم_گھماؤ" | "rotation_molle" | "weicher_spin" | "мягкое_вращение" =>
11822 {
11823 let id = self.arg_num(&args, 0, 0.)? as usize;
11824 let ax = self.arg_num(&args, 1, 0.)? as f32;
11825 let ay = self.arg_num(&args, 2, 0.)? as f32;
11826 let az = self.arg_num(&args, 3, 0.)? as f32;
11827 let rate = self.arg_num(&args, 4, 0.1)? as f32;
11828 if let Some(b) = self.soft_bodies.get_mut(id) {
11829 b.spin(ling_physics::Vec3::new(ax, ay, az), rate);
11830 }
11831 return Ok(Value::Unit);
11832 },
11833 #[cfg(not(target_arch = "wasm32"))]
11834 "soft_deform" | "形变量" | "変形量" | "변형량" | "ความบิดเบี้ยว" | "تغییرشکل_نرم" | "تشوه_ناعم" | "עיוות_רך" | "نرم_بگاڑ" | "déformer_mou" | "weiches_verformen" | "мягкая_деформация" =>
11835 {
11836 let id = self.arg_num(&args, 0, 0.)? as usize;
11837 let d = self
11838 .soft_bodies
11839 .get(id)
11840 .map(|b| b.deformation())
11841 .unwrap_or(0.0);
11842 return Ok(Value::Number(d as f64));
11843 },
11844 #[cfg(not(target_arch = "wasm32"))]
11847 "soft_angular_speed"
11848 | "软体角速"
11849 | "ソフト角速度"
11850 | "소프트각속도"
11851 | "ความเร็วเชิงมุมนุ่ม" | "سرعت_زاویهای_نرم" | "سرعة_زاوية_ناعمة" | "מהירות_זוויתית_רכה" | "نرم_زاویائی_رفتار" | "vitesse_angulaire_molle" | "weiche_winkelgeschwindigkeit" | "мягкая_угловая_скорость" => {
11852 let id = self.arg_num(&args, 0, 0.)? as usize;
11853 let w = self
11854 .soft_bodies
11855 .get(id)
11856 .map(|b| b.angular_speed())
11857 .unwrap_or(0.0);
11858 return Ok(Value::Number(w as f64));
11859 },
11860 #[cfg(not(target_arch = "wasm32"))]
11861 "soft_centroid" | "软体质心" | "ソフト重心" | "소프트중심" | "จุดศูนย์กลางนุ่ม" | "مرکز_جرم_نرم" | "مركز_ثقل_ناعم" | "מרכז_כובד_רך" | "نرم_مرکز_ثقل" | "centroïde_mou" | "weicher_schwerpunkt" | "мягкий_центроид" =>
11862 {
11863 let id = self.arg_num(&args, 0, 0.)? as usize;
11864 let c = self
11865 .soft_bodies
11866 .get(id)
11867 .map(|b| b.centroid())
11868 .unwrap_or(ling_physics::Vec3::ZERO);
11869 return Ok(Value::List(Rc::new(vec![
11870 Value::Number(c.x as f64),
11871 Value::Number(c.y as f64),
11872 Value::Number(c.z as f64),
11873 ])));
11874 },
11875 #[cfg(not(target_arch = "wasm32"))]
11877 "soft_nodes" | "软体节点" | "ソフト節点" | "소프트노드" | "จุดนุ่ม" | "گرههای_نرم" | "عقد_ناعمة" | "צמתי_רך" | "نرم_نوڈز" | "nœuds_mous" | "weiche_knoten" | "мягкие_узлы" =>
11878 {
11879 let id = self.arg_num(&args, 0, 0.)? as usize;
11880 let mut out = Vec::new();
11881 if let Some(b) = self.soft_bodies.get(id) {
11882 for n in &b.nodes {
11883 out.push(Value::Number(n.pos.x as f64));
11884 out.push(Value::Number(n.pos.y as f64));
11885 out.push(Value::Number(n.pos.z as f64));
11886 }
11887 }
11888 return Ok(Value::List(Rc::new(out)));
11889 },
11890
11891 #[cfg(not(target_arch = "wasm32"))]
11893 "rb_add" | "刚体添加" | "剛体追加" | "강체추가" | "เพิ่มวัตถุแข็ง" | "افزودن_جسم_صلب" | "أضف_جسما_صلبا" | "הוסף_גוף_קשיח" | "سخت_جسم_شامل_کرو" | "ajouter_corps_rigide" | "starrkörper_hinzufügen" | "добавить_твёрдое_тело" =>
11894 {
11895 let x = self.arg_num(&args, 0, 0.)? as f32;
11896 let y = self.arg_num(&args, 1, 0.)? as f32;
11897 let z = self.arg_num(&args, 2, 0.)? as f32;
11898 let mass = self.arg_num(&args, 3, 1.0)? as f32;
11899 let mut b =
11900 ling_physics::rigid::RigidBody::new(ling_physics::Vec3::new(x, y, z), mass);
11901 b.restitution = 0.6;
11902 return Ok(Value::Number(self.rigid_world.add(b) as f64));
11903 },
11904 #[cfg(not(target_arch = "wasm32"))]
11905 "rb_torque" | "扭矩" | "トルク" | "토크" | "แรงบิด" | "گشتاور" | "عزم_دوران" | "מומנט" | "ٹارک" | "couple_corps_rigide" | "starrkörper_drehmoment" | "крутящий_момент_твёрдого_тела" => {
11906 let i = self.arg_num(&args, 0, 0.)? as usize;
11907 let tx = self.arg_num(&args, 1, 0.)? as f32;
11908 let ty = self.arg_num(&args, 2, 0.)? as f32;
11909 let tz = self.arg_num(&args, 3, 0.)? as f32;
11910 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11911 b.apply_torque(ling_physics::Vec3::new(tx, ty, tz));
11912 }
11913 return Ok(Value::Unit);
11914 },
11915 #[cfg(not(target_arch = "wasm32"))]
11916 "rb_spin" | "自旋" | "スピン" | "스핀" | "หมุน" | "چرخش_جسم_صلب" | "دوران_جسم_صلب" | "סיבוב_גוף_קשיח" | "سخت_جسم_گھماؤ" | "spin_corps_rigide" | "starrkörper_spin" | "вращение_твёрдого_тела" => {
11917 let i = self.arg_num(&args, 0, 0.)? as usize;
11918 let wx = self.arg_num(&args, 1, 0.)? as f32;
11919 let wy = self.arg_num(&args, 2, 0.)? as f32;
11920 let wz = self.arg_num(&args, 3, 0.)? as f32;
11921 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11922 b.apply_spin(ling_physics::Vec3::new(wx, wy, wz));
11923 }
11924 return Ok(Value::Unit);
11925 },
11926 #[cfg(not(target_arch = "wasm32"))]
11927 "rb_impulse" | "刚体冲量" | "剛体インパルス" | "강체충격" | "แรงดลแข็ง" | "ضربه_جسم_صلب" | "دفعة_جسم_صلب" | "דחף_גוף_קשיח" | "سخت_جسم_دھکا" | "impulsion_corps_rigide" | "starrkörper_impuls" | "импульс_твёрдого_тела" =>
11928 {
11929 let i = self.arg_num(&args, 0, 0.)? as usize;
11930 let ix = self.arg_num(&args, 1, 0.)? as f32;
11931 let iy = self.arg_num(&args, 2, 0.)? as f32;
11932 let iz = self.arg_num(&args, 3, 0.)? as f32;
11933 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11934 b.apply_impulse(ling_physics::Vec3::new(ix, iy, iz));
11935 }
11936 return Ok(Value::Unit);
11937 },
11938 #[cfg(not(target_arch = "wasm32"))]
11939 "rb_floor" | "刚体落地" | "剛体着地" | "강체바닥" | "พื้นแข็ง" | "کف_جسم_صلب" | "أرضية_جسم_صلب" | "רצפת_גוף_קשיח" | "سخت_جسم_فرش" | "sol_corps_rigide" | "starrkörper_boden" | "пол_твёрдого_тела" =>
11940 {
11941 let i = self.arg_num(&args, 0, 0.)? as usize;
11942 let fy = self.arg_num(&args, 1, 0.)? as f32;
11943 let rest = self.arg_num(&args, 2, 0.6)? as f32;
11944 let fric = self.arg_num(&args, 3, 0.6)? as f32;
11945 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11946 b.bounce_floor(fy, rest, fric);
11947 }
11948 return Ok(Value::Unit);
11949 },
11950 #[cfg(not(target_arch = "wasm32"))]
11951 "rb_gravity" | "刚体重力" | "剛体重力" | "강체중력" | "แรงโน้มถ่วงแข็ง" | "گرانش_جسم_صلب" | "جاذبية_جسم_صلب" | "כבידת_גוף_קשיח" | "سخت_جسم_کشش_ثقل" | "gravité_corps_rigide" | "starrkörper_schwerkraft" | "гравитация_твёрдого_тела" =>
11952 {
11953 let gx = self.arg_num(&args, 0, 0.)? as f32;
11954 let gy = self.arg_num(&args, 1, 9.81)? as f32;
11955 let gz = self.arg_num(&args, 2, 0.)? as f32;
11956 self.rigid_world.gravity = ling_physics::Vec3::new(gx, gy, gz);
11957 return Ok(Value::Unit);
11958 },
11959 #[cfg(not(target_arch = "wasm32"))]
11960 "rb_step" | "刚体步进" | "剛体更新" | "강체스텝" | "ก้าวแข็ง" | "گام_جسم_صلب" | "خطوة_جسم_صلب" | "צעד_גוף_קשיח" | "سخت_جسم_قدم" | "pas_corps_rigide" | "starrkörper_schritt" | "шаг_твёрдого_тела" =>
11961 {
11962 let dt = self.arg_num(&args, 0, 0.016)? as f32;
11963 self.rigid_world.step(dt);
11964 return Ok(Value::Unit);
11965 },
11966 #[cfg(not(target_arch = "wasm32"))]
11967 "rb_pos" | "刚体位置" | "剛体位置" | "강체위치" | "ตำแหน่งแข็ง" | "موقعیت_جسم_صلب" | "موضع_جسم_صلب" | "מיקום_גוף_קשיח" | "سخت_جسم_مقام" | "position_corps_rigide" | "starrkörper_position" | "позиция_твёрдого_тела" =>
11968 {
11969 let i = self.arg_num(&args, 0, 0.)? as usize;
11970 let p = self
11971 .rigid_world
11972 .bodies
11973 .get(i)
11974 .map(|b| b.pos)
11975 .unwrap_or(ling_physics::Vec3::ZERO);
11976 return Ok(Value::List(Rc::new(vec![
11977 Value::Number(p.x as f64),
11978 Value::Number(p.y as f64),
11979 Value::Number(p.z as f64),
11980 ])));
11981 },
11982 #[cfg(not(target_arch = "wasm32"))]
11983 "rb_rot" | "刚体旋转" | "剛体回転" | "강체회전" | "การหมุนแข็ง" | "چرخش_وضعية_جسم_صلب" | "دوران_وضعية_جسم_صلب" | "סיבוב_זווית_גוף_קשיח" | "سخت_جسم_گردش" | "rotation_corps_rigide" | "starrkörper_rotation" | "поворот_твёрдого_тела" =>
11984 {
11985 let i = self.arg_num(&args, 0, 0.)? as usize;
11986 let q = self
11987 .rigid_world
11988 .bodies
11989 .get(i)
11990 .map(|b| b.orientation)
11991 .unwrap_or(ling_physics::Quat::IDENTITY);
11992 return Ok(Value::List(Rc::new(vec![
11993 Value::Number(q.x as f64),
11994 Value::Number(q.y as f64),
11995 Value::Number(q.z as f64),
11996 Value::Number(q.w as f64),
11997 ])));
11998 },
11999
12000 #[cfg(not(target_arch = "wasm32"))]
12002 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" | "بارگذاری_مش" | "حمّل_شبكة" | "טען_מש" | "میش_لوڈ" =>
12003 {
12004 let path = self.arg_str(&args, 0, "");
12005 let resolved = if std::path::Path::new(&path).exists() {
12006 path.clone()
12007 } else if let Some(d) = &self.source_dir {
12008 d.join(&path).to_string_lossy().into_owned()
12009 } else {
12010 path.clone()
12011 };
12012 let bytes = match std::fs::read(&resolved) {
12013 Ok(b) => b,
12014 Err(e) => {
12015 eprintln!("mesh_load failed ({path}): {e}");
12016 return Ok(Value::Number(-1.0));
12017 },
12018 };
12019 if bytes.len() < 16 || &bytes[0..4] != b"LMSH" {
12020 eprintln!("mesh_load: bad header ({path})");
12021 return Ok(Value::Number(-1.0));
12022 }
12023 let rd4 =
12024 |o: usize| -> [u8; 4] { [bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]] };
12025 let height = f32::from_le_bytes(rd4(8));
12026 let ntri = u32::from_le_bytes(rd4(12)) as usize;
12027 let need = 16usize.saturating_add(ntri.saturating_mul(9 * 4 + 3));
12028 if bytes.len() < need {
12029 eprintln!("mesh_load: truncated ({path})");
12030 return Ok(Value::Number(-1.0));
12031 }
12032 let mut pos = Vec::with_capacity(ntri * 3);
12033 let mut col = Vec::with_capacity(ntri);
12034 let mut off = 16usize;
12035 for _ in 0..ntri {
12036 for _k in 0..3 {
12037 let x = f32::from_le_bytes(rd4(off));
12038 let y = f32::from_le_bytes(rd4(off + 4));
12039 let z = f32::from_le_bytes(rd4(off + 8));
12040 off += 12;
12041 pos.push([x, y, z]);
12042 }
12043 col.push([bytes[off], bytes[off + 1], bytes[off + 2]]);
12044 off += 3;
12045 }
12046 eprintln!("mesh_load: {} ({} tris, h={:.2})", path, ntri, height);
12047 let id = self.meshes.len();
12048 self.meshes
12049 .push(crate::gfx::shapes::ColorMesh { pos, col, height });
12050 return Ok(Value::Number(id as f64));
12051 },
12052 #[cfg(target_arch = "wasm32")]
12053 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" | "بارگذاری_مش" | "حمّل_شبكة" | "טען_רשת" | "میش_لوڈ" =>
12054 {
12055 return Ok(Value::Number(-1.0));
12058 },
12059 #[cfg(not(target_arch = "wasm32"))]
12060 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" | "رسم_مش_رنگی" | "ارسم_شبكة_ملونة" | "צייר_רשת_צבעונית" | "رنگین_میش_کھینچو" =>
12061 {
12062 let id = self.arg_num(&args, 0, 0.)? as usize;
12064 let cx = self.arg_num(&args, 1, 0.)? as f32;
12065 let cy = self.arg_num(&args, 2, 0.)? as f32;
12066 let cz = self.arg_num(&args, 3, 0.)? as f32;
12067 let sc = self.arg_num(&args, 4, 1.)? as f32;
12068 let yaw = self.arg_num(&args, 5, 0.)? as f32;
12069 let sway = self.arg_num(&args, 6, 0.)? as f32;
12070 let arm = self.arg_num(&args, 7, 0.)? as f32;
12071 let lean = self.arg_num(&args, 8, 0.)? as f32;
12072 let leg = self.arg_num(&args, 9, 0.)? as f32;
12073 let tuck = self.arg_num(&args, 10, 0.)? as f32;
12074 if id < self.meshes.len() {
12075 let m = &self.meshes[id];
12076 let mut gfx = self.gfx.borrow_mut();
12077 gfx.draw_color_mesh(m, cx, cy, cz, sc, yaw, sway, arm, lean, leg, tuck);
12078 }
12079 return Ok(Value::Unit);
12080 },
12081 #[cfg(target_arch = "wasm32")]
12082 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" | "رسم_مش_رنگی" | "ارسم_شبكة_ملونة" | "צייר_רשת_צבעונית" | "رنگین_میش_کھینچو" =>
12083 {
12084 return Ok(Value::Unit);
12085 },
12086
12087 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" | "مایع_جدید" | "سائل_جديد" | "נוזל_חדש" | "نیا_مائع" | "nouveau_liquide" | "neue_flüssigkeit" | "новая_жидкость" =>
12089 {
12090 let w = self.arg_num(&args, 0, 64.)? as usize;
12091 let h = self.arg_num(&args, 1, 64.)? as usize;
12092 let id = self.liquids.len();
12093 self.liquids
12094 .push(ling_physics::liquid::LiquidGrid::new(w, h));
12095 return Ok(Value::Number(id as f64));
12096 },
12097 "liquid_set_colors" | "液体颜色" | "液体配色" | "액체색상" | "สีของเหลว" | "تنظیم_رنگ_مایع" | "عيّن_ألوان_السائل" | "קבע_צבעי_נוזל" | "مائع_رنگ_مقرر_کرو" =>
12098 {
12099 let id = self.arg_num(&args, 0, 0.)? as usize;
12100 let wr = self.arg_num(&args, 1, 40.)? as f32;
12101 let wg = self.arg_num(&args, 2, 110.)? as f32;
12102 let wb = self.arg_num(&args, 3, 235.)? as f32;
12103 let or_ = self.arg_num(&args, 4, 240.)? as f32;
12104 let og = self.arg_num(&args, 5, 175.)? as f32;
12105 let ob = self.arg_num(&args, 6, 45.)? as f32;
12106 if let Some(g) = self.liquids.get_mut(id) {
12107 g.set_colors(wr, wg, wb, or_, og, ob);
12108 }
12109 return Ok(Value::Unit);
12110 },
12111 "liquid_splat" | "液体注入" | "液体追加" | "액체분사" | "หยดของเหลว" | "پاشش_مایع" | "بقعة_سائل" | "התזת_נוזל" | "مائع_چھینٹا" | "éclaboussure_liquide" | "flüssigkeit_spritzer" | "брызги_жидкости" =>
12112 {
12113 let id = self.arg_num(&args, 0, 0.)? as usize;
12114 let x = self.arg_num(&args, 1, 0.)? as f32;
12115 let y = self.arg_num(&args, 2, 0.)? as f32;
12116 let kind = self.arg_num(&args, 3, 0.)? as i32;
12117 let amt = self.arg_num(&args, 4, 1.0)? as f32;
12118 let rad = self.arg_num(&args, 5, 4.0)? as f32;
12119 if let Some(g) = self.liquids.get_mut(id) {
12120 g.splat(x, y, kind, amt, rad);
12121 }
12122 return Ok(Value::Unit);
12123 },
12124 "liquid_gravity" | "液体重力" | "液体重力ベクトル" | "액체중력" | "แรงโน้มถ่วงเหลว" | "گرانش_مایع" | "جاذبية_السائل" | "כבידת_נוזל" | "مائع_کشش_ثقل" | "gravité_liquide" | "flüssigkeit_schwerkraft" | "гравитация_жидкости" =>
12125 {
12126 let id = self.arg_num(&args, 0, 0.)? as usize;
12127 let gx = self.arg_num(&args, 1, 0.)? as f32;
12128 let gy = self.arg_num(&args, 2, 60.)? as f32;
12129 if let Some(g) = self.liquids.get_mut(id) {
12130 g.set_gravity(gx, gy);
12131 }
12132 return Ok(Value::Unit);
12133 },
12134 "liquid_step" | "液体步进" | "液体更新" | "액체스텝" | "ก้าวของเหลว" | "گام_مایع" | "خطوة_السائل" | "צעד_נוזל" | "مائع_قدم" | "pas_liquide" | "flüssigkeit_schritt" | "шаг_жидкости" =>
12135 {
12136 let id = self.arg_num(&args, 0, 0.)? as usize;
12137 let dt = self.arg_num(&args, 1, 0.016)? as f32;
12138 if let Some(g) = self.liquids.get_mut(id) {
12139 g.step(dt);
12140 }
12141 return Ok(Value::Unit);
12142 },
12143 "liquid_step_all"
12149 | "液体全步进"
12150 | "液体全更新"
12151 | "전체액체스텝"
12152 | "ก้าวของเหลวทั้งหมด" | "گام_همه_مایعات" | "خطوة_كل_السوائل" | "צעד_כל_הנוזלים" | "تمام_مائع_قدم" => {
12153 let dt = self.arg_num(&args, 0, 0.016)? as f32;
12154 ling_physics::liquid::step_all(&mut self.liquids, dt);
12155 return Ok(Value::Unit);
12156 },
12157 "liquid_rainbow" | "液体彩虹" | "液体虹" | "액체무지개" | "ของเหลวสายรุ้ง" | "مایع_رنگینکمان" | "سائل_قوس_قزح" | "נוזל_קשת" | "قوس_قزح_مائع" | "arc_en_ciel_liquide" | "flüssigkeit_regenbogen" | "радуга_жидкости" =>
12159 {
12160 let id = self.arg_num(&args, 0, 0.)? as usize;
12161 let on = self.arg_num(&args, 1, 1.0)? > 0.5;
12162 if let Some(g) = self.liquids.get_mut(id) {
12163 g.rainbow = on;
12164 }
12165 return Ok(Value::Unit);
12166 },
12167 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" | "ترکیب_مایع" | "مزج_سائل" | "ערבוב_נוזל" | "مائع_ملاؤ" | "mélanger_liquide" | "flüssigkeit_mischen" | "смешать_жидкость" =>
12169 {
12170 let id = self.arg_num(&args, 0, 0.)? as usize;
12171 let m = self.liquids.get(id).map(|g| g.mix_amount()).unwrap_or(0.0);
12172 return Ok(Value::Number(m as f64));
12173 },
12174 #[cfg(not(target_arch = "wasm32"))]
12176 "liquid_draw" | "绘制液体" | "液体描画" | "액체그리기" | "วาดของเหลว" | "رسم_مایع" | "ارسم_سائلا" | "צייר_נוזל" | "مائع_کھینچو" | "dessiner_liquide" | "flüssigkeit_zeichnen" | "рисовать_жидкость" =>
12177 {
12178 let id = self.arg_num(&args, 0, 0.)? as usize;
12179 let sx = self.arg_num(&args, 1, 0.)? as i32;
12180 let sy = self.arg_num(&args, 2, 0.)? as i32;
12181 let scale = (self.arg_num(&args, 3, 4.)? as i32).max(1);
12182 if id < self.liquids.len() {
12183 let (gw, gh) = {
12184 let g = &self.liquids[id];
12185 (g.w, g.h)
12186 };
12187 let mut gfx = self.gfx.borrow_mut();
12188 let (w, h) = (gfx.width as i32, gfx.height as i32);
12189 let g = &self.liquids[id];
12190 for cy in 0..gh {
12191 for cx in 0..gw {
12192 let col = g.sample_rgb(cx, cy);
12193 let bx = sx + cx as i32 * scale;
12194 let by = sy + cy as i32 * scale;
12195 for dy in 0..scale {
12196 for dx in 0..scale {
12197 let px = bx + dx;
12198 let py = by + dy;
12199 if px >= 0 && py >= 0 && px < w && py < h {
12200 gfx.buffer[(py * w + px) as usize] = col;
12201 }
12202 }
12203 }
12204 }
12205 }
12206 }
12207 return Ok(Value::Unit);
12208 },
12209 "liquid_draw_surface" | "液体贴面" | "液体曲面" | "액체곡면" | "ของเหลวบนพื้นผิว" | "رسم_سطح_مایع" | "ارسم_سطح_السائل" | "צייר_משטח_נוזל" | "مائع_سطح_کھینچو" | "dessiner_surface_liquide" | "flüssigkeit_oberfläche_zeichnen" | "рисовать_поверхность_жидкости" =>
12212 {
12213 #[cfg(not(target_arch = "wasm32"))]
12214 {
12215 let id = self.arg_num(&args, 0, 0.)? as usize;
12216 let kind = self.arg_num(&args, 1, 1.)? as i32;
12217 let cx = self.arg_num(&args, 2, 0.)? as f32;
12218 let cy = self.arg_num(&args, 3, 0.)? as f32;
12219 let cz = self.arg_num(&args, 4, 0.)? as f32;
12220 let radius = self.arg_num(&args, 5, 2.0)? as f32;
12221 let height = self.arg_num(&args, 6, 3.0)? as f32;
12222 if id < self.liquids.len() {
12223 let (gw, gh) = {
12224 let g = &self.liquids[id];
12225 (g.w, g.h)
12226 };
12227 let mut gfx = self.gfx.borrow_mut();
12228 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
12229 let cam = gfx.camera.clone();
12230 let near = -cam.zdist + 0.05;
12231 let g = &self.liquids[id];
12232 let tau = std::f32::consts::TAU;
12233 let pi = std::f32::consts::PI;
12234 let sp = |u: f32, v: f32| -> [f32; 3] {
12236 if kind == 0 {
12237 [
12238 cx + (u - 0.5) * 2.0 * radius,
12239 cy,
12240 cz + (v - 0.5) * 2.0 * radius,
12241 ]
12242 } else if kind == 2 {
12243 let th = u * tau;
12244 [
12245 cx + th.cos() * radius,
12246 cy + (v - 0.5) * height,
12247 cz + th.sin() * radius,
12248 ]
12249 } else if kind == 3 {
12250 let th = u * tau;
12251 let rr = radius * (1.0 - v);
12252 [
12253 cx + th.cos() * rr,
12254 cy + (v - 0.5) * height,
12255 cz + th.sin() * rr,
12256 ]
12257 } else if kind == 4 {
12258 let th = u * tau;
12259 let ph = v * pi * 0.5;
12260 [
12261 cx + ph.sin() * th.cos() * radius,
12262 cy - ph.cos() * radius,
12263 cz + ph.sin() * th.sin() * radius,
12264 ]
12265 } else {
12266 let th = u * tau;
12267 let ph = v * pi;
12268 [
12269 cx + ph.sin() * th.cos() * radius,
12270 cy + ph.cos() * radius,
12271 cz + ph.sin() * th.sin() * radius,
12272 ]
12273 }
12274 };
12275 let nrm = |u: f32, v: f32| -> [f32; 3] {
12276 if kind == 0 {
12277 [0.0, -1.0, 0.0]
12278 } else if kind == 2 {
12279 let th = u * tau;
12280 [th.cos(), 0.0, th.sin()]
12281 } else if kind == 3 {
12282 let th = u * tau;
12283 let s = (radius / height.max(0.01)).atan();
12284 [th.cos() * s.cos(), s.sin(), th.sin() * s.cos()]
12285 } else if kind == 4 {
12286 let th = u * tau;
12287 let ph = v * pi * 0.5;
12288 [ph.sin() * th.cos(), -ph.cos(), ph.sin() * th.sin()]
12289 } else {
12290 let th = u * tau;
12291 let ph = v * pi;
12292 [ph.sin() * th.cos(), ph.cos(), ph.sin() * th.sin()]
12293 }
12294 };
12295 let gwf = gw as f32;
12296 let ghf = gh as f32;
12297 let mut cyc = 0usize;
12298 while cyc < gh {
12299 let mut cxc = 0usize;
12300 while cxc < gw {
12301 let uc = (cxc as f32 + 0.5) / gwf;
12303 let vc = (cyc as f32 + 0.5) / ghf;
12304 let c = sp(uc, vc);
12305 let n = nrm(uc, vc);
12306 let dc = cam.depth(c[0], c[1], c[2]);
12307 if dc > near {
12308 let cull = kind != 0
12309 && cam.depth(
12310 c[0] + n[0] * 0.06,
12311 c[1] + n[1] * 0.06,
12312 c[2] + n[2] * 0.06,
12313 ) > dc;
12314 if !cull {
12315 let u0 = cxc as f32 / gwf;
12317 let u1 = (cxc + 1) as f32 / gwf;
12318 let v0 = cyc as f32 / ghf;
12319 let v1 = (cyc + 1) as f32 / ghf;
12320 let q = [sp(u0, v0), sp(u1, v0), sp(u1, v1), sp(u0, v1)];
12321 let mut poly: Vec<[f32; 2]> = Vec::with_capacity(5);
12322 let mut ok = true;
12323 for p in &q {
12324 if cam.depth(p[0], p[1], p[2]) <= near {
12325 ok = false;
12326 break;
12327 }
12328 let (sx, sy, _) = cam.project(p[0], p[1], p[2]);
12329 poly.push([sx, sy]);
12330 }
12331 if ok {
12332 let p0 = poly[0];
12333 poly.push(p0);
12334 let col = g.sample_rgb(cxc, cyc);
12335 crate::gfx::raster::fill_contours_aa(
12336 &mut gfx.buffer,
12337 w,
12338 h,
12339 col,
12340 add,
12341 std::slice::from_ref(&poly),
12342 );
12343 }
12344 }
12345 }
12346 cxc += 1;
12347 }
12348 cyc += 1;
12349 }
12350 }
12351 }
12352 #[cfg(target_arch = "wasm32")]
12353 {
12354 }
12357 return Ok(Value::Unit);
12358 },
12359 #[cfg(not(target_arch = "wasm32"))]
12362 "sparkle" | "闪光" | "きらめき" | "반짝임" | "ประกาย" | "درخشش" | "بريق" | "נצנוץ" | "چمک" | "scintillement" | "funkeln" | "искриться" => {
12363 let x = self.arg_num(&args, 0, 0.)? as f32;
12364 let y = self.arg_num(&args, 1, 0.)? as f32;
12365 let ww = self.arg_num(&args, 2, 200.)? as f32;
12366 let hh = self.arg_num(&args, 3, 200.)? as f32;
12367 let count = self.arg_num(&args, 4, 40.)? as i32;
12368 let t = self.arg_num(&args, 5, 0.)? as f32;
12369 let mut gfx = self.gfx.borrow_mut();
12370 let (w, h, add, color) = (gfx.width, gfx.height, gfx.blend == 1, gfx.color);
12371 let (cr, cg, cb) = (
12372 (color >> 16 & 0xFF) as f32,
12373 (color >> 8 & 0xFF) as f32,
12374 (color & 0xFF) as f32,
12375 );
12376 let mut n = 0i32;
12377 while n < count {
12378 let hsh = (n as u32).wrapping_mul(2654435761).wrapping_add(0x9E3779B9);
12379 let u = ((hsh >> 8) & 1023) as f32 / 1023.0;
12380 let v = ((hsh >> 18) & 1023) as f32 / 1023.0;
12381 let phase = (hsh & 255) as f32 / 255.0;
12382 let tw = (t * 3.0 + phase * std::f32::consts::TAU + n as f32).sin() * 0.5 + 0.5;
12383 let sz = 1.5 + tw * 5.0;
12384 let px = x + u * ww;
12385 let py = y + v * hh;
12386 let b = tw * tw; let col =
12388 (((cr * b) as u32) << 16) | (((cg * b) as u32) << 8) | ((cb * b) as u32);
12389 crate::gfx::raster::draw_line_aa(
12390 &mut gfx.buffer,
12391 w,
12392 h,
12393 col,
12394 add,
12395 px - sz,
12396 py,
12397 px + sz,
12398 py,
12399 );
12400 crate::gfx::raster::draw_line_aa(
12401 &mut gfx.buffer,
12402 w,
12403 h,
12404 col,
12405 add,
12406 px,
12407 py - sz,
12408 px,
12409 py + sz,
12410 );
12411 let d = sz * 0.55;
12412 crate::gfx::raster::draw_line_aa(
12413 &mut gfx.buffer,
12414 w,
12415 h,
12416 col,
12417 add,
12418 px - d,
12419 py - d,
12420 px + d,
12421 py + d,
12422 );
12423 crate::gfx::raster::draw_line_aa(
12424 &mut gfx.buffer,
12425 w,
12426 h,
12427 col,
12428 add,
12429 px - d,
12430 py + d,
12431 px + d,
12432 py - d,
12433 );
12434 n += 1;
12435 }
12436 return Ok(Value::Unit);
12437 },
12438
12439 #[cfg(not(target_arch = "wasm32"))]
12445 "dialog_show" | "对话显示" | "会話表示" | "대화표시" | "แสดงบทสนทนา" | "نمایش_گفتگو" | "اعرض_الحوار" | "הצג_דיאלוג" | "مکالمہ_دکھاؤ" | "afficher_dialogue" | "dialog_anzeigen" | "показать_диалог" =>
12446 {
12447 let text = self.arg_str(&args, 0, "");
12448 let cps = self.arg_num(&args, 1, 32.0)? as f32;
12449 self.dialog = Some(ling_game::dialog::Dialog::new(&text, cps));
12450 return Ok(Value::Unit);
12451 },
12452 #[cfg(not(target_arch = "wasm32"))]
12453 "dialog_step" | "对话步进" | "会話更新" | "대화스텝" | "ก้าวบทสนทนา" | "گام_گفتگو" | "خطوة_الحوار" | "צעד_דיאלוג" | "مکالمہ_قدم" | "pas_dialogue" | "dialog_schritt" | "шаг_диалога" =>
12454 {
12455 let dt = self.arg_num(&args, 0, 0.016)? as f32;
12456 if let Some(d) = self.dialog.as_mut() {
12457 d.update(dt);
12458 }
12459 return Ok(Value::Unit);
12460 },
12461 #[cfg(not(target_arch = "wasm32"))]
12462 "dialog_advance" | "对话推进" | "会話送り" | "대화진행" | "เลื่อนบทสนทนา" | "پیشروی_گفتگو" | "تقدّم_الحوار" | "קדם_דיאלוג" | "مکالمہ_آگے_بڑھاؤ" | "avancer_dialogue" | "dialog_weiter" | "продолжить_диалог" =>
12463 {
12464 if let Some(d) = self.dialog.as_mut() {
12465 d.advance();
12466 }
12467 return Ok(Value::Unit);
12468 },
12469 #[cfg(not(target_arch = "wasm32"))]
12470 "dialog_active" | "对话激活" | "会話中" | "대화중" | "บทสนทนาทำงาน" | "گفتگو_فعال" | "الحوار_نشط" | "דיאלוג_פעיל" | "مکالمہ_فعال" | "dialogue_actif" | "dialog_aktiv" | "диалог_активен" =>
12471 {
12472 let a = self
12473 .dialog
12474 .as_ref()
12475 .map(|d| !d.is_closed())
12476 .unwrap_or(false);
12477 return Ok(Value::Bool(a));
12478 },
12479 #[cfg(not(target_arch = "wasm32"))]
12480 "dialog_typing" | "对话打字" | "会話タイプ中" | "대화타이핑" | "กำลังพิมพ์บทสนทนา" | "گفتگو_در_حال_تایپ" | "الحوار_يكتب" | "דיאלוג_מקליד" | "مکالمہ_ٹائپنگ" | "dialogue_frappe" | "dialog_tippen" | "диалог_печатает" =>
12481 {
12482 use ling_game::dialog::Dialog;
12483
12484 let a = self
12485 .dialog
12486 .as_ref()
12487 .map(|d: &Dialog| !d.is_closed() && d.is_typing())
12488 .unwrap_or(false);
12489 return Ok(Value::Bool(a));
12490 },
12491 #[cfg(not(target_arch = "wasm32"))]
12492 "dialog_close" | "对话关闭" | "会話閉じる" | "대화닫기" | "ปิดบทสนทนา" | "بستن_گفتگو" | "أغلق_الحوار" | "סגור_דיאלוג" | "مکالمہ_بند" | "fermer_dialogue" | "dialog_schließen" | "закрыть_диалог" =>
12493 {
12494 self.dialog = None;
12495 return Ok(Value::Unit);
12496 },
12497 #[cfg(not(target_arch = "wasm32"))]
12499 "dialog_color" | "对话颜色" | "会話色" | "대화색" | "สีบทสนทนา" | "رنگ_گفتگو" | "لون_الحوار" | "צבע_דיאלוג" | "مکالمہ_رنگ" | "couleur_dialogue" | "dialog_farbe" | "цвет_диалога" =>
12500 {
12501 let role = (self.arg_num(&args, 0, 0.0)? as usize).min(3);
12502 let r = self.arg_num(&args, 1, 255.0)? as u32 & 0xFF;
12503 let g = self.arg_num(&args, 2, 255.0)? as u32 & 0xFF;
12504 let b = self.arg_num(&args, 3, 255.0)? as u32 & 0xFF;
12505 self.dialog_colors[role] = (r << 16) | (g << 8) | b;
12506 return Ok(Value::Unit);
12507 },
12508 #[cfg(not(target_arch = "wasm32"))]
12510 "dialog_draw" | "对话绘制" | "会話描画" | "대화그리기" | "วาดบทสนทนา" | "رسم_گفتگو" | "ارسم_الحوار" | "צייר_דיאלוג" | "مکالمہ_کھینچو" | "dessiner_dialogue" | "dialog_zeichnen" | "рисовать_диалог" =>
12511 {
12512 let x = self.arg_num(&args, 0, 40.0)? as f32;
12513 let y = self.arg_num(&args, 1, 0.0)? as f32;
12514 let ww = self.arg_num(&args, 2, 720.0)? as f32;
12515 let hh = self.arg_num(&args, 3, 150.0)? as f32;
12516 let font = self.arg_num(&args, 4, -1.0)? as i64;
12517 let t = (crate::runtime::now_secs() - self.start_time_secs) as f32;
12518 self.render_dialog(x, y, ww, hh, font, t);
12519 return Ok(Value::Unit);
12520 },
12521
12522 #[cfg(not(target_arch = "wasm32"))]
12527 "text_poll" => {
12528 const TEXT_BUFFER_MAX: usize = 240;
12529 #[cfg(windows)]
12538 {
12539 let topmost = self.gfx.borrow().topmost_window;
12540 if topmost {
12541 if !window_is_foreground(self.gfx.borrow().hwnd) {
12542 return Ok(Value::Str(self.text_buffer.clone()));
12543 }
12544 let shift = os_key_down(VK_SHIFT);
12545 let now = crate::runtime::now_secs();
12546 let mut gfx = self.gfx.borrow_mut();
12547 let back_idx = VK_BACK as usize;
12548 let back_down = os_key_down(VK_BACK);
12549 let back_was = gfx.raw_keys_prev[back_idx];
12550 let (mut back_since, mut back_fire) = (
12551 gfx.raw_keys_down_since[back_idx],
12552 gfx.raw_keys_last_fire[back_idx],
12553 );
12554 if key_repeat_fire(now, back_down, back_was, &mut back_since, &mut back_fire) {
12555 self.text_buffer.pop();
12556 }
12557 gfx.raw_keys_down_since[back_idx] = back_since;
12558 gfx.raw_keys_last_fire[back_idx] = back_fire;
12559 gfx.raw_keys_prev[back_idx] = back_down;
12560 for &vk in TEXT_POLL_VKS {
12561 let idx = (vk as usize) & 0xFF;
12562 let down = os_key_down(vk);
12563 let was = gfx.raw_keys_prev[idx];
12564 let (mut since, mut fire) =
12565 (gfx.raw_keys_down_since[idx], gfx.raw_keys_last_fire[idx]);
12566 if key_repeat_fire(now, down, was, &mut since, &mut fire) {
12567 if let Some(c) = vk_char(vk, shift) {
12568 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12569 self.text_buffer.push(c);
12570 }
12571 }
12572 }
12573 gfx.raw_keys_down_since[idx] = since;
12574 gfx.raw_keys_last_fire[idx] = fire;
12575 gfx.raw_keys_prev[idx] = down;
12576 }
12577 return Ok(Value::Str(self.text_buffer.clone()));
12578 }
12579 }
12580 let (keys, shift) = {
12581 let gfx = self.gfx.borrow();
12582 match gfx.window.as_ref() {
12583 Some(w) => (
12584 w.get_keys_pressed(minifb::KeyRepeat::Yes),
12585 w.is_key_down(minifb::Key::LeftShift)
12586 || w.is_key_down(minifb::Key::RightShift),
12587 ),
12588 None => (Vec::new(), false),
12589 }
12590 };
12591 for k in keys {
12592 if k == minifb::Key::Backspace {
12593 self.text_buffer.pop();
12594 } else if let Some(c) = key_char(k, shift) {
12595 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12596 self.text_buffer.push(c);
12597 }
12598 }
12599 }
12600 return Ok(Value::Str(self.text_buffer.clone()));
12601 },
12602 #[cfg(target_arch = "wasm32")]
12603 "text_poll" => {
12604 return Ok(Value::Str(self.text_buffer.clone()));
12605 },
12606 "text_get" => return Ok(Value::Str(self.text_buffer.clone())),
12607 "text_set" => {
12608 self.text_buffer = self.arg_str(&args, 0, "");
12609 return Ok(Value::Unit);
12610 },
12611 "text_clear" => {
12612 self.text_buffer.clear();
12613 return Ok(Value::Unit);
12614 },
12615 #[cfg(not(target_arch = "wasm32"))]
12617 "record_frame" => {
12618 let n = self.record_n;
12619 let (buf, w, h) = {
12620 let gfx = self.gfx.borrow();
12621 (gfx.buffer.clone(), gfx.width, gfx.height)
12622 };
12623 let _ = std::fs::create_dir_all("recordings");
12624 let mut out = Vec::with_capacity(w * h * 3 + 32);
12625 out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
12626 for px in &buf {
12627 let p = *px;
12628 out.push((p >> 16) as u8);
12629 out.push((p >> 8) as u8);
12630 out.push(p as u8);
12631 }
12632 let _ = std::fs::write(format!("recordings/frame_{n:05}.ppm"), out);
12633 self.record_n += 1;
12634 return Ok(Value::Number(n as f64));
12635 },
12636 "record_count" => return Ok(Value::Number(self.record_n as f64)),
12637 #[cfg(not(target_arch = "wasm32"))]
12639 "screenshot" | "บันทึกภาพ" | "عکسصفحه" | "لقطة_شاشة" | "צילום_מסך" | "اسکرین_شاٹ" => {
12640 let mode = self.arg_str(&args, 0, "game");
12641 let (buf, w, h) = {
12642 let gfx = self.gfx.borrow();
12643 (gfx.buffer.clone(), gfx.width, gfx.height)
12644 };
12645 let _ = std::fs::create_dir_all("screenshots");
12646 let ts = std::time::SystemTime::now()
12647 .duration_since(std::time::UNIX_EPOCH)
12648 .map(|d| d.as_secs())
12649 .unwrap_or(0);
12650 let safe: String = mode
12651 .chars()
12652 .map(|c| if c.is_alphanumeric() { c } else { '_' })
12653 .collect();
12654 let path = format!("screenshots/ss_{ts}_{safe}_{w}x{h}.png");
12655 let mut rgb = Vec::with_capacity(w * h * 3);
12656 for px in &buf {
12657 let p = *px;
12658 rgb.push((p >> 16) as u8);
12659 rgb.push((p >> 8) as u8);
12660 rgb.push(p as u8);
12661 }
12662 if let Some(img) = image::RgbImage::from_raw(w as u32, h as u32, rgb) {
12663 let _ = img.save(&path);
12664 }
12665 return Ok(Value::Str(path));
12666 },
12667 #[cfg(not(target_arch = "wasm32"))]
12671 "mic_capture" => {
12672 if let Some(mic) = self.mic.as_ref() {
12673 let s = mic.latest_samples();
12674 self.mic_buffer.extend_from_slice(&s);
12675 let cap = 96_000usize; if self.mic_buffer.len() > cap {
12677 let drop = self.mic_buffer.len() - cap;
12678 self.mic_buffer.drain(0..drop);
12679 }
12680 }
12681 return Ok(Value::Number(self.mic_buffer.len() as f64));
12682 },
12683 #[cfg(not(target_arch = "wasm32"))]
12685 "mic_seed" => {
12686 let mut bytes = Vec::with_capacity(self.mic_buffer.len() * 4);
12687 for f in &self.mic_buffer {
12688 bytes.extend_from_slice(&f.to_le_bytes());
12689 }
12690 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(&bytes))));
12691 },
12692 #[cfg(not(target_arch = "wasm32"))]
12693 "mic_clear" => {
12694 self.mic_buffer.clear();
12695 return Ok(Value::Number(0.0));
12696 },
12697 #[cfg(not(target_arch = "wasm32"))]
12700 "flush_3d" | "render_3d" => {
12701 let mut gfx = self.gfx.borrow_mut();
12702 if !gfx.depth_queue.is_empty() {
12703 let w = gfx.width;
12704 let h = gfx.height;
12705 let dt = gfx.depth_test;
12706 let reset_z = gfx.zbuf_needs_clear;
12707 let (bm, ba) = (gfx.blend, gfx.alpha);
12708 let aa = gfx.antialias;
12709 let queue = std::mem::take(&mut gfx.depth_queue);
12710 {
12711 let g = &mut *gfx;
12712 let z = if dt { Some(&mut g.depth_buf) } else { None };
12713 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12714 }
12715 gfx.zbuf_needs_clear = false;
12716 gfx.depth_queue.set_state(bm, ba); }
12718 return Ok(Value::Unit);
12719 },
12720 #[cfg(target_arch = "wasm32")]
12721 "flush_3d" | "render_3d" => {
12722 let mut gfx = self.gfx.borrow_mut();
12723 if !gfx.depth_queue.is_empty() {
12724 let w = gfx.width;
12725 let h = gfx.height;
12726 let dt = gfx.depth_test;
12727 let reset_z = gfx.zbuf_needs_clear;
12728 let (bm, ba) = (gfx.blend, gfx.alpha);
12729 let aa = gfx.antialias;
12730 let queue = std::mem::take(&mut gfx.depth_queue);
12731 {
12732 let g = &mut *gfx;
12733 let z = if dt { Some(&mut g.depth_buf) } else { None };
12734 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12735 }
12736 gfx.zbuf_needs_clear = false;
12737 gfx.depth_queue.set_state(bm, ba);
12738 }
12739 return Ok(Value::Unit);
12740 },
12741
12742 "flush_post" | "post_now" | "포스트플러시" | "后期冲刷" => {
12747 let mut gfx = self.gfx.borrow_mut();
12748 if !gfx.depth_queue.is_empty() {
12749 let w = gfx.width;
12750 let h = gfx.height;
12751 let dt = gfx.depth_test;
12752 let reset_z = gfx.zbuf_needs_clear;
12753 let (bm, ba) = (gfx.blend, gfx.alpha);
12754 let aa = gfx.antialias;
12755 let queue = std::mem::take(&mut gfx.depth_queue);
12756 {
12757 let g = &mut *gfx;
12758 let z = if dt { Some(&mut g.depth_buf) } else { None };
12759 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12760 }
12761 gfx.zbuf_needs_clear = false;
12762 gfx.depth_queue.set_state(bm, ba);
12763 }
12764 gfx.toon_post_process();
12765 gfx.post_done = true;
12766 return Ok(Value::Unit);
12767 },
12768
12769 #[cfg(not(target_arch = "wasm32"))]
12772 "screen_distort" | "บิดจอ" | "屏幕扭曲" | "画面歪み" | "화면왜곡" | "اعوجاج_صفحه" | "شوّه_الشاشة" | "עוות_מסך" | "اسکرین_ڈسٹورٹ" =>
12773 {
12774 let amount = self.arg_num(&args, 0, 8.0)? as f32;
12775 let t = self.arg_num(&args, 1, 0.0)? as f32;
12776 let step = self.arg_num(&args, 2, 1.0)?.max(1.0) as usize;
12779 let _d = std::time::Instant::now();
12780 self.gfx.borrow_mut().distort(amount, t, step);
12781 ling_phase_add(phase::DISTORT, _d.elapsed().as_nanos());
12782 return Ok(Value::Unit);
12783 },
12784
12785 "set_rim" | "设置边缘光" | "リム設定" | "림라이트" | "ตั้งขอบเรือง" | "تنظیم_نور_لبه" | "عيّن_إضاءة_الحافة" | "קבע_תאורת_קצה" | "رم_لائٹ_مقرر_کرو" | "définir_contour_lumineux" | "rimlicht_setzen" | "задать_контурный_свет" =>
12786 {
12787 let s = self.arg_num(&args, 0, 0.6)? as f32;
12788 let r = self.arg_num(&args, 1, 115.)? as f32 / 255.0;
12789 let g = self.arg_num(&args, 2, 217.)? as f32 / 255.0;
12790 let b = self.arg_num(&args, 3, 255.)? as f32 / 255.0;
12791 let mut gfx = self.gfx.borrow_mut();
12792 gfx.shade.rim = s;
12793 gfx.shade.rim_color = [r, g, b];
12794 return Ok(Value::Unit);
12795 },
12796
12797 n if crate::gfx::shapes::canon(n).is_some() => {
12806 let kind = crate::gfx::shapes::canon(n).unwrap();
12807 let cx = self.arg_num(&args, 0, 0.)? as f32;
12808 let cy = self.arg_num(&args, 1, 0.)? as f32;
12809 let cz = self.arg_num(&args, 2, 0.)? as f32;
12810 let sx = self.arg_num(&args, 3, 1.)? as f32;
12811 let sy = self.arg_num(&args, 4, 1.)? as f32;
12812 let sz = self.arg_num(&args, 5, 1.)? as f32;
12813 let rx = self.arg_num(&args, 6, 0.)? as f32;
12814 let ry = self.arg_num(&args, 7, 0.)? as f32;
12815 let rz = self.arg_num(&args, 8, 0.)? as f32;
12816 let mode = self.arg_num(&args, 9, 0.)? as i32;
12817 let e0 = self.arg_num(&args, 10, 0.)? as f32;
12818 let e1 = self.arg_num(&args, 11, 0.)? as f32;
12819 let e2 = self.arg_num(&args, 12, 0.)? as f32;
12820 if let Some(mesh) = crate::gfx::shapes::build(
12821 kind,
12822 [cx, cy, cz, sx, sy, sz, rx, ry, rz],
12823 e0,
12824 e1,
12825 e2,
12826 ) {
12827 let mut gfx = self.gfx.borrow_mut();
12828 gfx.emit_mesh(&mesh, mode);
12829 }
12830 return Ok(Value::Unit);
12831 },
12832
12833 _ => {},
12834 }
12835
12836 if let Some(field_names) = self.structs.get(name).cloned() {
12838 if args.len() != field_names.len() {
12839 return Err(EvalErr::from(format!(
12840 "{name} expects {} field(s), got {}",
12841 field_names.len(),
12842 args.len()
12843 )));
12844 }
12845 let fields = field_names.into_iter().zip(args).collect();
12846 return Ok(Value::Struct { name: name.to_string(), fields });
12847 }
12848
12849 if let Some((enum_name, arity)) = self.enum_variants.get(name).cloned() {
12851 if args.len() != arity {
12852 return Err(EvalErr::from(format!(
12853 "{name} expects {arity} value(s), got {}",
12854 args.len()
12855 )));
12856 }
12857 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
12858 return Ok(Value::Variant { enum_name, variant, payload: args });
12859 }
12860
12861 #[cfg(target_arch = "wasm32")]
12862 if let Some(v) = wasm_unsupported_builtin(name) {
12863 return Ok(v);
12864 }
12865
12866 Err(EvalErr::from(format!("unknown function '{name}'")))
12867 }
12868
12869 fn call_value(&mut self, v: Value, args: Vec<Value>) -> EvalResult {
12870 match v {
12871 Value::Fn(params, body, mut captured) => {
12872 for (p, a) in params.iter().zip(args) {
12873 captured.insert(p.clone(), a);
12874 }
12875 match self.framed("<closure>", |me| me.exec_block(&body, &mut captured)) {
12876 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
12877 Err(EvalErr::Return(v)) => Ok(v),
12878 Err(e) => Err(e),
12879 }
12880 },
12881 other => Err(EvalErr::from(format!("cannot call {:?}", other))),
12882 }
12883 }
12884
12885 fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
12886 match (&recv, method) {
12887 (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
12888 (Value::Str(s), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(s.len() as f64)),
12895 (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
12896 (Value::Str(s), "contains" | "包含") => {
12897 if let Some(Value::Str(sub)) = args.first() {
12898 Ok(Value::Bool(s.contains(sub.as_str())))
12899 } else {
12900 Ok(Value::Bool(false))
12901 }
12902 },
12903 (Value::Str(s), "push_str" | "推_文") => {
12904 let mut s2 = s.clone();
12905 if let Some(Value::Str(a)) = args.first() {
12906 s2.push_str(a);
12907 }
12908 Ok(Value::Str(s2))
12909 },
12910 (Value::List(v), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(v.len() as f64)),
12911 (Value::List(v), "push" | "推" | "添加" | "追加" | "추가" | "เพิ่ม") => {
12912 let mut v2: Vec<Value> = (**v).clone();
12913 if let Some(a) = args.first() {
12914 v2.push(a.clone());
12915 }
12916 Ok(Value::List(Rc::new(v2)))
12917 },
12918 (Value::Struct { fields, .. }, _) if args.is_empty() => fields
12920 .iter()
12921 .find(|(k, _)| k == method)
12922 .map(|(_, v)| v.clone())
12923 .ok_or_else(|| EvalErr::from(format!("no field '{method}' on {recv}"))),
12924 (Value::Variant { variant, .. }, "tag" | "标签" | "タグ" | "태그" | "ป้าย")
12926 if args.is_empty() =>
12927 {
12928 Ok(Value::Str(variant.clone()))
12929 },
12930 (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
12931 _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
12932 }
12933 }
12934
12935 fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
12938 match (pat, val) {
12939 (Pattern::Wildcard, _) => Some(new_env()),
12940 (Pattern::Str(s), Value::Str(v)) if s == v => Some(new_env()),
12941 (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(new_env()),
12942 (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(new_env()),
12943 (Pattern::Ident(name), _) => {
12944 let mut e = new_env();
12945 e.insert(name.clone(), val.clone());
12946 Some(e)
12947 },
12948 (Pattern::Constructor(ctor, inner_pat), _) => {
12949 let (matches, inner_val) = match (ctor.as_str(), val) {
12950 ("ok" | "好", Value::Ok(v)) => (true, Some(v.as_ref().clone())),
12951 ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
12952 ("ok" | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
12953 _ => (false, None),
12954 };
12955 if !matches {
12956 return None;
12957 }
12958 match (inner_pat, inner_val) {
12959 (Some(p), Some(v)) => self.match_pattern(p, &v),
12960 (None, _) => Some(new_env()),
12961 (Some(p), None) => self.match_pattern(p, &Value::Unit),
12962 }
12963 },
12964 (Pattern::Variant(vname, sub_pats), Value::Variant { variant, payload, .. }) => {
12966 if vname != variant || sub_pats.len() != payload.len() {
12967 return None;
12968 }
12969 let mut bindings = new_env();
12970 for (p, v) in sub_pats.iter().zip(payload.iter()) {
12971 bindings.extend(self.match_pattern(p, v)?);
12972 }
12973 Some(bindings)
12974 },
12975 (Pattern::Variant(vname, sub), Value::Ok(v)) if (vname == "ok" || vname == "好") => {
12978 match sub.as_slice() {
12979 [] => Some(new_env()),
12980 [p] => self.match_pattern(p, v),
12981 _ => None,
12982 }
12983 },
12984 (Pattern::Variant(vname, sub), Value::Err(v))
12985 if (vname == "bad" || vname == "坏" || vname == "err") =>
12986 {
12987 match sub.as_slice() {
12988 [] => Some(new_env()),
12989 [p] => self.match_pattern(p, v),
12990 _ => None,
12991 }
12992 },
12993 _ => None,
12994 }
12995 }
12996
12997 fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
13000 match val {
13001 Value::List(v) => Ok(Rc::try_unwrap(v).unwrap_or_else(|rc| (*rc).clone())),
13002 Value::Str(s) => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
13003 Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
13004 other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
13005 }
13006 }
13007
13008 pub(crate) fn is_truthy(&self, val: &Value) -> bool {
13009 match val {
13010 Value::Bool(b) => *b,
13011 Value::Unit => false,
13012 Value::Number(n) => *n != 0.0,
13013 Value::Str(s) => !s.is_empty(),
13014 Value::List(v) => !v.is_empty(),
13015 Value::Ok(_) => true,
13016 Value::Err(_) => false,
13017 Value::Fn(_, _, _) => true,
13018 Value::Struct { .. } => true,
13019 Value::Variant { .. } => true,
13020 }
13021 }
13022
13023 fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
13024 match val {
13025 Value::Number(n) => Ok(*n),
13026 Value::Str(s) => s
13027 .parse()
13028 .map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
13029 other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
13030 }
13031 }
13032
13033 fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
13035 match args.get(n) {
13036 Some(v) => self.to_number(v),
13037 None => Ok(default),
13038 }
13039 }
13040
13041 fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
13042 args.get(n)
13043 .map(|v| v.to_string())
13044 .unwrap_or_else(|| default.to_string())
13045 }
13046
13047 #[allow(dead_code)]
13049 fn arg_list_f32(&self, args: &[Value], n: usize) -> Vec<f32> {
13050 match args.get(n) {
13051 Some(Value::List(v)) => v
13052 .iter()
13053 .filter_map(|x| match x {
13054 Value::Number(n) => Some(*n as f32),
13055 _ => None,
13056 })
13057 .collect(),
13058 _ => Vec::new(),
13059 }
13060 }
13061
13062 #[cfg(not(target_arch = "wasm32"))]
13065 fn color_at(&self, args: &[Value], i: usize, default: u32) -> u32 {
13066 match (args.get(i), args.get(i + 1), args.get(i + 2)) {
13067 (Some(a), Some(b), Some(c)) => {
13068 match (self.to_number(a), self.to_number(b), self.to_number(c)) {
13069 (Ok(r), Ok(g), Ok(bl)) => {
13070 ((r as u32 & 0xFF) << 16) | ((g as u32 & 0xFF) << 8) | (bl as u32 & 0xFF)
13071 },
13072 _ => default,
13073 }
13074 },
13075 _ => default,
13076 }
13077 }
13078
13079 #[cfg(not(target_arch = "wasm32"))]
13081 fn pitch_arg(&self, args: &[Value], i: usize, default: i32) -> i32 {
13082 match args.get(i) {
13083 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(default),
13084 Some(Value::Number(n)) => *n as i32,
13085 _ => default,
13086 }
13087 }
13088
13089 #[cfg(not(target_arch = "wasm32"))]
13091 fn mouse_now(&self) -> (f32, f32, bool) {
13092 let gfx = self.gfx.borrow();
13093 let (mx, my) = gfx
13094 .window
13095 .as_ref()
13096 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
13097 .unwrap_or((0.0, 0.0));
13098 let down = gfx
13099 .window
13100 .as_ref()
13101 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
13102 .unwrap_or(false);
13103 (mx, my, down)
13104 }
13105
13106 #[cfg(not(target_arch = "wasm32"))]
13110 fn draw_ui(&self, d: &ling_ui::widgets::Draw) {
13111 let mut gfx = self.gfx.borrow_mut();
13112 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
13113 for (c, poly) in &d.fills {
13114 crate::gfx::raster::fill_contours_aa(
13115 &mut gfx.buffer,
13116 w,
13117 h,
13118 *c,
13119 add,
13120 std::slice::from_ref(poly),
13121 );
13122 }
13123 for (c, pl) in &d.strokes {
13124 for s in pl.windows(2) {
13125 crate::gfx::raster::draw_line_aa(
13126 &mut gfx.buffer,
13127 w,
13128 h,
13129 *c,
13130 add,
13131 s[0][0],
13132 s[0][1],
13133 s[1][0],
13134 s[1][1],
13135 );
13136 }
13137 }
13138 }
13139
13140 fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
13142 let tx = self.arg_num(args, 0, 0.0)? as usize;
13143 let ty = self.arg_num(args, 1, 0.0)? as usize;
13144 let tw = self.arg_num(args, 2, 256.0)? as usize;
13145 let th = self.arg_num(args, 3, 256.0)? as usize;
13146 Ok((tx, ty, tw.max(1), th.max(1)))
13147 }
13148
13149 pub(crate) fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
13150 match op {
13151 BinOp::Add => match (l, r) {
13152 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
13153 (Value::Str(a), Value::Str(b)) => Ok(Value::Str(a + &b)),
13154 (Value::Str(a), b) => Ok(Value::Str(a + &b.to_string())),
13155 (a, Value::Str(b)) => Ok(Value::Str(a.to_string() + &b)),
13156 (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
13157 },
13158 BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
13159 BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
13160 BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
13161 BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
13162 BinOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
13163 BinOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
13164 BinOp::Lt => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
13165 BinOp::Gt => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
13166 BinOp::Le => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
13167 BinOp::Ge => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
13168 BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
13169 BinOp::Or => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
13170 }
13171 }
13172
13173 fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
13174 if args.is_empty() {
13175 return Ok(String::new());
13176 }
13177 let fmt = match &args[0] {
13178 Value::Str(s) => s.clone(),
13179 other => return Ok(other.to_string()),
13180 };
13181
13182 let mut result = String::new();
13183 let mut arg_idx = 1usize;
13184 let mut chars = fmt.chars().peekable();
13185 while let Some(c) = chars.next() {
13186 if c == '{' {
13187 if chars.peek() == Some(&'}') {
13188 chars.next();
13189 if arg_idx < args.len() {
13190 result.push_str(&args[arg_idx].to_string());
13191 arg_idx += 1;
13192 }
13193 } else {
13194 let mut spec = String::new();
13195 for ch in chars.by_ref() {
13196 if ch == '}' {
13197 break;
13198 }
13199 spec.push(ch);
13200 }
13201 if arg_idx < args.len() {
13202 if let Some(suffix) = spec.strip_prefix(":.") {
13203 if let Value::Number(n) = &args[arg_idx] {
13204 let prec: usize =
13205 suffix.trim_end_matches('f').parse().unwrap_or(2);
13206 result.push_str(&format!("{:.prec$}", n));
13207 arg_idx += 1;
13208 continue;
13209 }
13210 }
13211 result.push_str(&args[arg_idx].to_string());
13212 arg_idx += 1;
13213 }
13214 }
13215 } else {
13216 result.push(c);
13217 }
13218 }
13219 Ok(result)
13220 }
13221}
13222
13223#[cfg(not(target_arch = "wasm32"))]
13224#[cfg(not(target_arch = "wasm32"))]
13226fn parse_pad_button(name: &str) -> Option<ling_input::GamepadButton> {
13227 use ling_input::GamepadButton as B;
13228 Some(match name.to_ascii_lowercase().as_str() {
13229 "a" | "south" | "cross" => B::South,
13230 "b" | "east" | "circle" => B::East,
13231 "x" | "west" | "square" => B::West,
13232 "y" | "north" | "triangle" => B::North,
13233 "lb" | "l1" | "left_shoulder" => B::LeftShoulder,
13234 "rb" | "r1" | "right_shoulder" => B::RightShoulder,
13235 "lt" | "l2" | "left_trigger" => B::LeftTrigger,
13236 "rt" | "r2" | "right_trigger" => B::RightTrigger,
13237 "start" | "menu" | "options" | "démarrer" | "начать" => B::Start,
13238 "select" | "back" | "share" | "view" => B::Select,
13239 "guide" | "home" => B::Guide,
13240 "l3" | "left_stick" => B::LeftStick,
13241 "r3" | "right_stick" => B::RightStick,
13242 "up" | "dpad_up" => B::DpadUp,
13243 "down" | "dpad_down" => B::DpadDown,
13244 "left" | "dpad_left" => B::DpadLeft,
13245 "right" | "dpad_right" => B::DpadRight,
13246 _ => return None,
13247 })
13248}
13249
13250#[cfg(not(target_arch = "wasm32"))]
13251fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
13252 use minifb::Key;
13253 Some(match name {
13254 "numpad0" | "kp0" => Key::NumPad0,
13255 "numpad1" | "kp1" => Key::NumPad1,
13256 "numpad2" | "kp2" => Key::NumPad2,
13257 "numpad3" | "kp3" => Key::NumPad3,
13258 "numpad4" | "kp4" => Key::NumPad4,
13259 "numpad5" | "kp5" => Key::NumPad5,
13260 "numpad6" | "kp6" => Key::NumPad6,
13261 "numpad7" | "kp7" => Key::NumPad7,
13262 "numpad8" | "kp8" => Key::NumPad8,
13263 "numpad9" | "kp9" => Key::NumPad9,
13264 "numpad+" | "kp+" => Key::NumPadPlus,
13265 "numpad-" | "kp-" => Key::NumPadMinus,
13266 "numpad*" | "kp*" => Key::NumPadAsterisk,
13267 "numpad/" | "kp/" => Key::NumPadSlash,
13268 "left" => Key::Left,
13269 "right" => Key::Right,
13270 "up" => Key::Up,
13271 "down" => Key::Down,
13272 "space" => Key::Space,
13273 "enter" => Key::Enter,
13274 "escape" => Key::Escape,
13275 "pageup" => Key::PageUp,
13276 "pagedown" => Key::PageDown,
13277 "lshift" | "leftshift" => Key::LeftShift,
13278 "rshift" | "rightshift" => Key::RightShift,
13279 "lctrl" | "leftctrl" => Key::LeftCtrl,
13280 "rctrl" | "rightctrl" => Key::RightCtrl,
13281 "lalt" | "leftalt" => Key::LeftAlt,
13282 "ralt" | "rightalt" => Key::RightAlt,
13283 "tab" => Key::Tab,
13284 "backspace" => Key::Backspace,
13285 "delete" => Key::Delete,
13286 "insert" => Key::Insert,
13287 "home" => Key::Home,
13288 "end" => Key::End,
13289 "a" => Key::A,
13290 "b" => Key::B,
13291 "c" => Key::C,
13292 "d" => Key::D,
13293 "e" => Key::E,
13294 "f" => Key::F,
13295 "g" => Key::G,
13296 "h" => Key::H,
13297 "i" => Key::I,
13298 "j" => Key::J,
13299 "k" => Key::K,
13300 "l" => Key::L,
13301 "m" => Key::M,
13302 "n" => Key::N,
13303 "o" => Key::O,
13304 "p" => Key::P,
13305 "q" => Key::Q,
13306 "r" => Key::R,
13307 "s" => Key::S,
13308 "t" => Key::T,
13309 "u" => Key::U,
13310 "v" => Key::V,
13311 "w" => Key::W,
13312 "x" => Key::X,
13313 "y" => Key::Y,
13314 "z" => Key::Z,
13315 "0" => Key::Key0,
13316 "1" => Key::Key1,
13317 "2" => Key::Key2,
13318 "3" => Key::Key3,
13319 "4" => Key::Key4,
13320 "5" => Key::Key5,
13321 "6" => Key::Key6,
13322 "7" => Key::Key7,
13323 "8" => Key::Key8,
13324 "9" => Key::Key9,
13325 _ => return None,
13326 })
13327}
13328
13329pub(crate) fn values_equal(a: &Value, b: &Value) -> bool {
13330 match (a, b) {
13331 (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
13332 (Value::Str(x), Value::Str(y)) => x == y,
13333 (Value::Bool(x), Value::Bool(y)) => x == y,
13334 (Value::Unit, Value::Unit) => true,
13335 _ => false,
13336 }
13337}
13338
13339#[cfg(all(not(target_arch = "wasm32"), windows))]
13348fn make_borderless_fullscreen(hwnd: isize, screen_w: i32, screen_h: i32) {
13349 if hwnd == 0 {
13350 return;
13351 }
13352 unsafe {
13353 extern "system" {
13354 fn SetWindowLongPtrW(hwnd: isize, index: i32, new: isize) -> isize;
13355 fn SetWindowPos(
13356 hwnd: isize,
13357 insert_after: isize,
13358 x: i32,
13359 y: i32,
13360 cx: i32,
13361 cy: i32,
13362 flags: u32,
13363 ) -> i32;
13364 fn ShowWindow(hwnd: isize, cmd: i32) -> i32;
13365 }
13366 const GWL_STYLE: i32 = -16;
13367 const GWL_EXSTYLE: i32 = -20;
13368 SetWindowLongPtrW(hwnd, GWL_STYLE, 0x9000_0000isize);
13371 SetWindowLongPtrW(hwnd, GWL_EXSTYLE, 0);
13373 SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0020 | 0x0040);
13375 ShowWindow(hwnd, 3); }
13377}
13378
13379#[cfg(all(not(target_arch = "wasm32"), windows))]
13393fn force_window_focus(hwnd: isize) {
13394 if hwnd == 0 {
13395 return;
13396 }
13397 unsafe {
13398 extern "system" {
13399 fn GetForegroundWindow() -> isize;
13400 fn GetWindowThreadProcessId(hwnd: isize, pid: *mut u32) -> u32;
13401 fn GetCurrentThreadId() -> u32;
13402 fn AttachThreadInput(id_attach: u32, id_attach_to: u32, attach: i32) -> i32;
13403 fn SetForegroundWindow(hwnd: isize) -> i32;
13404 fn BringWindowToTop(hwnd: isize) -> i32;
13405 fn SetFocus(hwnd: isize) -> isize;
13406 fn SetActiveWindow(hwnd: isize) -> isize;
13407 }
13408 let fg = GetForegroundWindow();
13409 if fg != 0 && fg != hwnd {
13410 let mut fg_pid: u32 = 0;
13411 let fg_tid = GetWindowThreadProcessId(fg, &mut fg_pid);
13412 let my_tid = GetCurrentThreadId();
13413 if fg_tid != 0 && fg_tid != my_tid {
13414 AttachThreadInput(my_tid, fg_tid, 1);
13415 SetForegroundWindow(hwnd);
13416 BringWindowToTop(hwnd);
13417 SetFocus(hwnd);
13418 SetActiveWindow(hwnd);
13419 AttachThreadInput(my_tid, fg_tid, 0);
13420 return;
13421 }
13422 }
13423 SetForegroundWindow(hwnd);
13424 BringWindowToTop(hwnd);
13425 SetFocus(hwnd);
13426 SetActiveWindow(hwnd);
13427 }
13428}
13429
13430#[cfg(all(not(target_arch = "wasm32"), windows))]
13435fn set_window_topmost(hwnd: isize, topmost: bool) {
13436 if hwnd == 0 {
13437 return;
13438 }
13439 unsafe {
13440 extern "system" {
13441 fn SetWindowPos(
13442 hwnd: isize,
13443 insert_after: isize,
13444 x: i32,
13445 y: i32,
13446 cx: i32,
13447 cy: i32,
13448 flags: u32,
13449 ) -> i32;
13450 }
13451 let insert_after: isize = if topmost { -1 } else { -2 }; SetWindowPos(hwnd, insert_after, 0, 0, 0, 0, 0x0002 | 0x0001 | 0x0010);
13455 }
13456}
13457
13458#[cfg(not(target_arch = "wasm32"))]
13464fn apply_frame_pacing(win: &mut minifb::Window, vsync: bool) {
13465 match std::env::var("LING_FPS_CAP")
13466 .ok()
13467 .and_then(|v| v.parse::<usize>().ok())
13468 {
13469 Some(0) => win.set_target_fps(100_000),
13470 Some(cap) => win.set_target_fps(cap),
13471 None if vsync => win.set_target_fps(monitor_info().2.max(30) as usize),
13472 None => win.set_target_fps(100_000),
13473 }
13474}
13475
13476#[cfg(all(not(target_arch = "wasm32"), windows))]
13479fn monitor_info() -> (i32, i32, i32) {
13480 unsafe {
13481 extern "system" {
13482 fn GetSystemMetrics(index: i32) -> i32;
13483 fn GetDC(hwnd: isize) -> isize;
13484 fn ReleaseDC(hwnd: isize, hdc: isize) -> i32;
13485 fn GetDeviceCaps(hdc: isize, index: i32) -> i32;
13486 }
13487 let w = GetSystemMetrics(0).max(1); let h = GetSystemMetrics(1).max(1); let hdc = GetDC(0);
13490 let mut hz = if hdc != 0 { GetDeviceCaps(hdc, 116) } else { 0 }; if hdc != 0 {
13492 ReleaseDC(0, hdc);
13493 }
13494 if hz <= 1 {
13495 hz = 60; }
13497 (w, h, hz)
13498 }
13499}
13500
13501#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13505fn monitor_info() -> (i32, i32, i32) {
13506 let (w, h) = native_screen_size();
13507 (w as i32, h as i32, linux_refresh_hz().unwrap_or(60))
13508}
13509
13510#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13515fn linux_refresh_hz() -> Option<i32> {
13516 let out = std::process::Command::new("xrandr")
13517 .arg("--current")
13518 .output()
13519 .ok()?;
13520 if !out.status.success() {
13521 return None;
13522 }
13523 parse_xrandr_max_hz(&String::from_utf8_lossy(&out.stdout))
13524}
13525
13526#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13529fn parse_xrandr_max_hz(text: &str) -> Option<i32> {
13530 text.split_whitespace()
13531 .filter(|tok| tok.contains('*'))
13532 .filter_map(|tok| {
13533 tok.trim_matches(|c: char| !c.is_ascii_digit() && c != '.')
13534 .parse::<f64>()
13535 .ok()
13536 })
13537 .map(|hz| hz.round() as i32)
13538 .filter(|&hz| (24..=1000).contains(&hz))
13539 .max()
13540}
13541
13542#[cfg(target_arch = "wasm32")]
13544fn monitor_info() -> (i32, i32, i32) {
13545 let (w, h) = crate::gfx::webgl::canvas_size();
13546 (w as i32, h as i32, 60)
13547}
13548
13549#[cfg(all(test, not(target_arch = "wasm32"), not(windows)))]
13550mod xrandr_tests {
13551 use super::parse_xrandr_max_hz;
13552
13553 #[test]
13554 fn picks_highest_active_output() {
13555 let text = "\
13556eDP-1 connected primary 1920x1080+0+0
13557 1920x1080 60.00*+ 59.94
13558DP-1 connected 2560x1440+1920+0
13559 2560x1440 144.00*+ 120.00 60.00
13560";
13561 assert_eq!(parse_xrandr_max_hz(text), Some(144));
13562 }
13563
13564 #[test]
13565 fn single_output() {
13566 let text = " 1920x1080 75.00*+ 60.00\n";
13567 assert_eq!(parse_xrandr_max_hz(text), Some(75));
13568 }
13569
13570 #[test]
13571 fn no_active_mode_returns_none() {
13572 let text = "eDP-1 disconnected\n";
13573 assert_eq!(parse_xrandr_max_hz(text), None);
13574 }
13575
13576 #[test]
13577 fn out_of_range_hz_filtered() {
13578 let text = " 1x1 5000.00*+\n";
13579 assert_eq!(parse_xrandr_max_hz(text), None);
13580 }
13581}
13582
13583#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13586fn native_screen_size() -> (f64, f64) {
13587 (1920.0, 1080.0)
13590}
13591
13592struct LingProfileState {
13604 enabled: bool,
13605 every: u64,
13606 frames: u64,
13607 calls: std::collections::HashMap<String, (u64, u128)>, }
13609
13610thread_local! {
13611 static LING_PROFILE: std::cell::RefCell<LingProfileState> = std::cell::RefCell::new({
13612 let enabled = std::env::var("LING_PROFILE").map(|v| v != "0" && !v.is_empty()).unwrap_or(false);
13613 let every = std::env::var("LING_PROFILE_EVERY").ok()
13614 .and_then(|v| v.parse::<u64>().ok()).filter(|&n| n > 0).unwrap_or(240);
13615 if enabled {
13616 eprintln!("[ling-profile] ON — report every {every} frames (set LING_PROFILE_EVERY to change)");
13617 }
13618 LingProfileState { enabled, every, frames: 0, calls: std::collections::HashMap::new() }
13619 });
13620}
13621
13622#[inline]
13623fn ling_profile_enabled() -> bool {
13624 LING_PROFILE.with(|p| p.borrow().enabled)
13625}
13626
13627thread_local! {
13628 static LING_FPS: std::cell::RefCell<(bool, f64, u32, f64)> = std::cell::RefCell::new(
13629 (std::env::var("LING_FPS").map(|v| v != "0" && !v.is_empty()).unwrap_or(false), 0.0, 0, 0.0)
13630 );
13631}
13632
13633#[cfg(not(target_arch = "wasm32"))]
13634fn ling_fps_tick() {
13635 LING_FPS.with(|s| {
13636 let mut s = s.borrow_mut();
13637 if !s.0 {
13638 return;
13639 }
13640 let now = crate::runtime::now_secs();
13641 if s.1 > 0.0 {
13642 s.3 += now - s.1;
13643 s.2 += 1;
13644 if s.2 >= 120 {
13645 let avg = s.3 / s.2 as f64;
13646 eprintln!(
13647 "[fps] {:.1} fps ({:.2} ms/frame, wall, {} frames)",
13648 1.0 / avg,
13649 avg * 1000.0,
13650 s.2
13651 );
13652 s.2 = 0;
13653 s.3 = 0.0;
13654 }
13655 }
13656 s.1 = now;
13657 });
13658}
13659
13660thread_local! {
13665 static LING_PHASE: std::cell::RefCell<(bool, u64, [u128; 5])> = std::cell::RefCell::new(
13666 (std::env::var_os("LING_PHASE").is_some(), 0, [0; 5])
13667 );
13668}
13669
13670pub mod phase {
13672 pub const FLUSH: usize = 0;
13673 pub const TOON: usize = 1;
13674 pub const BLIT: usize = 2;
13675 pub const DISTORT: usize = 3;
13676 pub const SORT: usize = 4;
13677}
13678
13679#[cfg(not(target_arch = "wasm32"))]
13680#[inline]
13681pub fn ling_phase_add(idx: usize, nanos: u128) {
13682 LING_PHASE.with(|p| {
13683 let mut p = p.borrow_mut();
13684 if p.0 {
13685 p.2[idx] += nanos;
13686 }
13687 });
13688}
13689
13690#[cfg(not(target_arch = "wasm32"))]
13691fn ling_phase_frame() {
13692 LING_PHASE.with(|p| {
13693 let mut p = p.borrow_mut();
13694 if !p.0 {
13695 return;
13696 }
13697 p.1 += 1;
13698 if p.1 >= 120 {
13699 let f = p.1 as f64;
13700 let ms = |i: usize| p.2[i] as f64 / 1e6 / f;
13701 eprintln!(
13702 "[phase] sort={:.2} flush={:.2} toon={:.2} blit={:.2} distort={:.2} ms/frame",
13703 ms(phase::SORT),
13704 ms(phase::FLUSH),
13705 ms(phase::TOON),
13706 ms(phase::BLIT),
13707 ms(phase::DISTORT)
13708 );
13709 p.1 = 0;
13710 p.2 = [0; 5];
13711 }
13712 });
13713}
13714
13715fn ling_profile_record(name: &str, nanos: u128) {
13716 let is_frame = matches!(
13718 name,
13719 "present" | "แสดงผล" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
13720 );
13721 LING_PROFILE.with(|p| {
13722 let mut p = p.borrow_mut();
13723 let e = p.calls.entry(name.to_string()).or_insert((0, 0));
13724 e.0 += 1;
13725 e.1 += nanos;
13726 if is_frame {
13727 p.frames += 1;
13728 if p.frames % p.every == 0 {
13729 ling_profile_print(&p);
13730 }
13731 }
13732 });
13733}
13734
13735fn ling_profile_print(p: &LingProfileState) {
13736 let mut rows: Vec<(&String, u64, u128)> =
13737 p.calls.iter().map(|(n, (c, ns))| (n, *c, *ns)).collect();
13738 use std::cmp::Reverse;
13739 rows.sort_by_key(|x| Reverse(x.2)); let total_ns: u128 = p.calls.values().map(|(_, ns)| *ns).sum();
13741 let total_calls: u64 = p.calls.values().map(|(c, _)| *c).sum();
13742 let fr = p.frames.max(1) as f64;
13743 eprintln!(
13744 "\n┌─ LING PROFILE ── frames={} ─ builtin calls by total inclusive time ─────────────",
13745 p.frames
13746 );
13747 eprintln!(
13748 "│ {:<24} {:>9} {:>9} {:>10} {:>9} {:>6}",
13749 "builtin", "calls", "calls/fr", "total_ms", "ms/frame", "%time"
13750 );
13751 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13752 for (name, count, ns) in rows.iter().take(30) {
13753 let ms = *ns as f64 / 1e6;
13754 let pct = if total_ns > 0 {
13755 *ns as f64 / total_ns as f64 * 100.0
13756 } else {
13757 0.0
13758 };
13759 eprintln!(
13760 "│ {:<24} {:>9} {:>9.1} {:>10.1} {:>9.3} {:>5.1}%",
13761 truncate_name(name),
13762 count,
13763 *count as f64 / fr,
13764 ms,
13765 ms / fr,
13766 pct
13767 );
13768 }
13769 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13770 eprintln!(
13771 "│ TOTAL {} builtin calls, {:.1} ms over {} frames → {:.0} calls/frame, {:.2} ms/frame in builtins",
13772 total_calls,
13773 total_ns as f64 / 1e6,
13774 p.frames,
13775 total_calls as f64 / fr,
13776 total_ns as f64 / 1e6 / fr
13777 );
13778 eprintln!("└──────────────────────────────────────────────────────────────────────────────");
13779}
13780
13781fn truncate_name(s: &str) -> String {
13784 let max = 24;
13785 if s.chars().count() <= max {
13786 s.to_string()
13787 } else {
13788 let mut t: String = s.chars().take(max - 1).collect();
13789 t.push('…');
13790 t
13791 }
13792}