1#[cfg(not(target_arch = "wasm32"))]
3mod ai;
4#[cfg(not(target_arch = "wasm32"))]
5mod gamepad;
6#[cfg(target_arch = "wasm32")]
7mod input_web;
8#[cfg(not(target_arch = "wasm32"))]
9pub(crate) mod jit_abi;
10
11#[cfg(not(target_arch = "wasm32"))]
14pub fn init_aot_runtime() {
15 let interp = Interpreter::new();
16 jit_abi::init(interp);
17}
18
19pub fn now_secs() -> f64 {
22 #[cfg(target_arch = "wasm32")]
23 {
24 js_sys::Date::now() / 1000.0
25 }
26 #[cfg(not(target_arch = "wasm32"))]
27 {
28 std::time::SystemTime::now()
29 .duration_since(std::time::UNIX_EPOCH)
30 .map(|d| d.as_secs_f64())
31 .unwrap_or(0.0)
32 }
33}
34
35static LINE_HUE_RATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
39static LINE_HUE_START: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
40
41pub fn set_line_hue_rate(rate: f64) {
43 LINE_HUE_RATE.store(rate.to_bits(), std::sync::atomic::Ordering::Relaxed);
44 LINE_HUE_START.store(now_secs().to_bits(), std::sync::atomic::Ordering::Relaxed);
45}
46
47pub fn line_hue_phase() -> Option<f64> {
51 let rate = f64::from_bits(LINE_HUE_RATE.load(std::sync::atomic::Ordering::Relaxed));
52 if rate <= 0.0 {
53 return None;
54 }
55 let start = f64::from_bits(LINE_HUE_START.load(std::sync::atomic::Ordering::Relaxed));
56 Some((now_secs() - start) * rate)
57}
58
59#[cfg(target_arch = "wasm32")]
62thread_local! {
63 static WASM_MODULES: std::cell::RefCell<std::collections::HashMap<String, String>> =
64 std::cell::RefCell::new(std::collections::HashMap::new());
65}
66
67#[cfg(target_arch = "wasm32")]
70pub fn register_wasm_module(path: &str, source: &str) {
71 WASM_MODULES.with(|m| m.borrow_mut().insert(path.to_string(), source.to_string()));
72}
73
74#[cfg(target_arch = "wasm32")]
76pub(crate) fn get_wasm_module(path: &str) -> Option<String> {
77 WASM_MODULES.with(|m| m.borrow().get(path).cloned())
78}
79
80#[cfg(target_arch = "wasm32")]
81#[inline]
82fn wasm_sleep_ms(ms: i32) {
83 if ms <= 0 {
84 return;
85 }
86
87 let global = js_sys::global();
90 let has_sab = js_sys::Reflect::has(
91 &global,
92 &wasm_bindgen::JsValue::from_str("SharedArrayBuffer"),
93 )
94 .unwrap_or(false);
95 let has_atomics =
96 js_sys::Reflect::has(&global, &wasm_bindgen::JsValue::from_str("Atomics")).unwrap_or(false);
97 if has_sab && has_atomics {
98 let sab = js_sys::SharedArrayBuffer::new(4);
99 let i32a = js_sys::Int32Array::new(&sab);
100 if js_sys::Atomics::wait_with_timeout(&i32a, 0, 0, ms as f64).is_ok() {
101 return;
102 }
103 }
104
105 let end = js_sys::Date::now() + ms as f64;
106 while js_sys::Date::now() < end {}
107}
108
109#[cfg(target_arch = "wasm32")]
110fn wasm_fetch_sync(
111 path: &str,
112 response_type: &str,
113 return_expr: &str,
114) -> Result<wasm_bindgen::JsValue, String> {
115 let quoted = js_sys::JSON::stringify(&wasm_bindgen::JsValue::from_str(path))
116 .ok()
117 .and_then(|s| s.as_string())
118 .unwrap_or_else(|| "\"\"".to_string());
119
120 let script = format!(
121 "(function(){{\n var xhr = new XMLHttpRequest();\n xhr.open('GET', {quoted}, false);\n xhr.responseType = '{response_type}';\n xhr.send(null);\n if ((xhr.status|0) !== 200 && (xhr.status|0) !== 0) {{ throw new Error('HTTP ' + xhr.status + ' for ' + {quoted}); }}\n return {return_expr};\n}})()"
122 );
123
124 js_sys::eval(&script).map_err(|e| {
125 e.as_string()
126 .unwrap_or_else(|| format!("JS eval failed: {:?}", e))
127 })
128}
129
130#[cfg(target_arch = "wasm32")]
131fn wasm_fetch_bytes(path: &str) -> Result<Vec<u8>, String> {
132 let value = wasm_fetch_sync(
133 path,
134 "arraybuffer",
135 "new Uint8Array(xhr.response || new ArrayBuffer(0))",
136 )?;
137 let arr = js_sys::Uint8Array::new(&value);
138 let mut out = vec![0u8; arr.length() as usize];
139 arr.copy_to(&mut out);
140 Ok(out)
141}
142
143#[cfg(target_arch = "wasm32")]
144fn wasm_fetch_text(path: &str) -> Result<String, String> {
145 let value = wasm_fetch_sync(path, "text", "String(xhr.responseText || '')")?;
146 Ok(value.as_string().unwrap_or_default())
147}
148
149#[cfg(not(target_arch = "wasm32"))]
150mod net;
151#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
152pub mod web;
153use crate::gfx::{GfxState, Light};
154use crate::parser::ast::*;
155#[cfg(target_arch = "wasm32")]
156use js_sys;
157use std::cell::RefCell;
158use std::collections::HashMap;
159use std::rc::Rc;
160use crate::gfx::raster::draw_line;
163#[cfg(not(target_arch = "wasm32"))]
164use crate::gfx::raster::fill_triangle;
165#[cfg(not(target_arch = "wasm32"))]
166use ling_audio::{AudioEngine, ToneParams, Wave};
167
168#[cfg(not(target_arch = "wasm32"))]
169use ling_audio::FftAnalyzer;
170
171#[cfg(not(target_arch = "wasm32"))]
172use ling_mic;
173
174#[derive(Debug, Clone)]
177pub enum Value {
178 Str(String),
179 Number(f64),
180 Bool(bool),
181 Unit,
182 List(Rc<Vec<Value>>),
183 Ok(Box<Value>),
184 Err(Box<Value>),
185 Fn(Vec<String>, Vec<Stmt>, Env),
186 Struct {
188 name: String,
189 fields: Vec<(String, Value)>,
190 },
191 Variant {
193 enum_name: String,
194 variant: String,
195 payload: Vec<Value>,
196 },
197}
198
199use rustc_hash::FxHashMap;
202type Env = FxHashMap<String, Value>;
203
204#[inline]
205fn new_env() -> Env {
206 FxHashMap::default()
207}
208
209impl std::fmt::Display for Value {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 match self {
212 Value::Str(s) => write!(f, "{s}"),
213 Value::Number(n) => {
214 if n.fract() == 0.0 && n.abs() < 1e15 {
215 write!(f, "{}", *n as i64)
216 } else {
217 write!(f, "{n}")
218 }
219 },
220 Value::Bool(b) => write!(f, "{b}"),
221 Value::Unit => write!(f, "()"),
222 Value::List(v) => {
223 write!(f, "[")?;
224 for (i, x) in v.iter().enumerate() {
225 if i > 0 {
226 write!(f, ", ")?;
227 }
228 write!(f, "{x}")?;
229 }
230 write!(f, "]")
231 },
232 Value::Ok(v) => write!(f, "Ok({v})"),
233 Value::Err(v) => write!(f, "Err({v})"),
234 Value::Fn(_, _, _) => write!(f, "<fn>"),
235 Value::Struct { name, fields } => {
236 write!(f, "{name} {{ ")?;
237 for (i, (k, v)) in fields.iter().enumerate() {
238 if i > 0 {
239 write!(f, ", ")?;
240 }
241 write!(f, "{k}: {v}")?;
242 }
243 write!(f, " }}")
244 },
245 Value::Variant { variant, payload, .. } => {
246 write!(f, "{variant}")?;
247 if !payload.is_empty() {
248 write!(f, "(")?;
249 for (i, v) in payload.iter().enumerate() {
250 if i > 0 {
251 write!(f, ", ")?;
252 }
253 write!(f, "{v}")?;
254 }
255 write!(f, ")")?;
256 }
257 Ok(())
258 },
259 }
260 }
261}
262
263#[cfg(target_arch = "wasm32")]
264fn wasm_unsupported_builtin(name: &str) -> Option<Value> {
265 Some(match name {
266 "audio_blip"
268 | "提示音"
269 | "ビープ音"
270 | "효과음"
271 | "เสียงบี๊บ"
272 | "ui_sound"
273 | "界面音"
274 | "UI音"
275 | "인터페이스음"
276 | "เสียงปุ่ม"
277 | "audio_stop_sfx"
278 | "停止音效"
279 | "効果音停止"
280 | "효과음정지"
281 | "หยุดเอฟเฟกต์ทั้งหมด" | "بوق_کوتاه" | "نغمة_قصيرة" | "ביפ" | "بلپ_آواز" => Value::Unit,
282
283 "music_load"
285 | "载入音乐"
286 | "音楽読込"
287 | "음악로드"
288 | "โหลดเพลง"
289 | "music_patch"
290 | "乐器音色"
291 | "音色読込"
292 | "악기패치"
293 | "แพตช์เครื่องดนตรี"
294 | "music_lrc"
295 | "载入歌词"
296 | "歌詞読込"
297 | "가사로드"
298 | "โหลดเนื้อเพลง"
299 | "music_midi_load"
300 | "载入MIDI"
301 | "MIDI読込"
302 | "미디로드"
303 | "โหลดมิดี" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" => Value::Number(-1.0),
304
305 "music_duration"
306 | "音乐时长"
307 | "音楽長さ"
308 | "음악길이"
309 | "ความยาวเพลง"
310 | "music_bpm"
311 | "节拍速度"
312 | "テンポ"
313 | "템포"
314 | "จังหวะต่อนาที"
315 | "music_pos"
316 | "音乐位置"
317 | "音楽位置"
318 | "음악위치"
319 | "ตำแหน่งเพลง"
320 | "music_mic_pitch"
321 | "麦克风音高"
322 | "マイク音程"
323 | "마이크음정"
324 | "ระดับเสียงไมค์"
325 | "music_hz"
326 | "音符频率"
327 | "音符周波数"
328 | "음표주파수"
329 | "ความถี่โน้ต"
330 | "music_pitch_score"
331 | "音准评分"
332 | "音程スコア"
333 | "음정점수"
334 | "คะแนนเสียง"
335 | "music_judge"
336 | "判定"
337 | "判定する"
338 | "판정"
339 | "ตัดสินจังหวะ"
340 | "music_midi_count"
341 | "MIDI数量"
342 | "MIDI数"
343 | "미디수"
344 | "จำนวนมิดี" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" => Value::Number(0.0),
345
346 "music_key"
347 | "调性"
348 | "調性"
349 | "조성"
350 | "คีย์เพลง"
351 | "music_lyric"
352 | "当前歌词"
353 | "現在歌詞"
354 | "현재가사"
355 | "เนื้อเพลงปัจจุบัน"
356 | "music_note_name"
357 | "音名"
358 | "音名称"
359 | "음이름"
360 | "ชื่อโน้ต"
361 | "music_grade_name"
362 | "判定名"
363 | "判定名称"
364 | "판정이름"
365 | "ชื่อการตัดสิน" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" => Value::Str(String::new()),
366
367 "music_onsets"
368 | "音符起点"
369 | "オンセット"
370 | "온셋"
371 | "จุดเริ่มเสียง"
372 | "music_beat_grid"
373 | "节拍网格"
374 | "ビートグリッド"
375 | "비트그리드"
376 | "กริดจังหวะ"
377 | "music_midi_notes"
378 | "MIDI音符"
379 | "MIDIノート"
380 | "미디음표"
381 | "โน้ตมิดี"
382 | "music_midi_bars"
383 | "MIDI音条"
384 | "MIDIバー"
385 | "미디바"
386 | "แท่งมิดี"
387 | "music_fft"
388 | "音乐频谱"
389 | "音楽スペクトル"
390 | "음악스펙트럼"
391 | "สเปกตรัมเพลง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" => Value::List(Vec::new().into()),
392
393 "music_play"
394 | "播放音乐"
395 | "音楽再生"
396 | "음악재생"
397 | "เล่นเพลง"
398 | "music_pause"
399 | "暂停音乐"
400 | "音楽一時停止"
401 | "음악일시정지"
402 | "หยุดเพลงชั่วคราว"
403 | "music_stop"
404 | "停止音乐"
405 | "音楽停止"
406 | "음악정지"
407 | "หยุดเพลง"
408 | "music_seek"
409 | "定位音乐"
410 | "音楽シーク"
411 | "음악탐색"
412 | "ค้นหาเพลง"
413 | "music_volume"
414 | "音乐音量"
415 | "音楽音量"
416 | "음악음량"
417 | "ระดับเพลง"
418 | "music_note"
419 | "弹音符"
420 | "音符演奏"
421 | "음표연주"
422 | "เล่นโน้ต"
423 | "music_note_on"
424 | "音符开始"
425 | "音符オン"
426 | "음표켜기"
427 | "โน้ตเริ่ม"
428 | "music_note_off"
429 | "音符结束"
430 | "音符オフ"
431 | "음표끄기"
432 | "โน้ตจบ" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" => Value::Unit,
433
434 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" | "مایع_جدید" | "سائل_جديد" | "נוזל_חדש" | "نیا_مائع" => {
436 Value::Number(0.0)
437 },
438 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" | "ترکیب_مایع" | "مزج_سائل" | "ערבוב_נוזל" | "مائع_ملاؤ" => {
439 Value::Number(0.0)
440 },
441 "liquid_set_colors"
442 | "液体颜色"
443 | "液体配色"
444 | "액체색상"
445 | "สีของเหลว"
446 | "liquid_splat"
447 | "液体注入"
448 | "液体追加"
449 | "액체분사"
450 | "หยดของเหลว"
451 | "liquid_gravity"
452 | "液体重力"
453 | "液体重力ベクトル"
454 | "액체중력"
455 | "แรงโน้มถ่วงเหลว"
456 | "liquid_step"
457 | "液体步进"
458 | "液体更新"
459 | "액체스텝"
460 | "ก้าวของเหลว"
461 | "liquid_step_all"
462 | "液体全步进"
463 | "液体全更新"
464 | "전체액체스텝"
465 | "ก้าวของเหลวทั้งหมด"
466 | "liquid_rainbow"
467 | "液体彩虹"
468 | "液体虹"
469 | "액체무지개"
470 | "ของเหลวสายรุ้ง"
471 | "liquid_draw"
472 | "绘制液体"
473 | "液体描画"
474 | "액체그리기"
475 | "วาดของเหลว"
476 | "liquid_draw_surface"
477 | "液体贴面"
478 | "液体曲面"
479 | "액체곡면"
480 | "ของเหลวบนพื้นผิว" | "تنظیم_رنگ_مایع" | "عيّن_ألوان_السائل" | "קבע_צבעי_נוזל" | "مائع_رنگ_مقرر_کرو" => Value::Unit,
481
482 "nn_new"
484 | "建神经网"
485 | "ニューラル作成"
486 | "신경망생성"
487 | "สร้างโครงข่าย"
488 | "nn_load"
489 | "载入网"
490 | "網読込"
491 | "신경망불러오기"
492 | "โหลดโครงข่าย" | "شبکه_جدید" | "شبكة_جديدة" | "רשת_חדשה" | "نئی_نیورل_نیٹ" => Value::Number(-1.0),
493 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" | "پیشروی_شبکه" | "تمرير_أمامي" | "העברה_קדימה" | "فارورڈ_پاس" => {
494 Value::List(Vec::new().into())
495 },
496 "nn_train"
497 | "训练网"
498 | "ニューラル学習"
499 | "신경망학습"
500 | "ฝึกโครงข่าย"
501 | "nn_dense"
502 | "密集层"
503 | "密層追加"
504 | "밀집층"
505 | "ชั้นหนาแน่น" | "آموزش_شبکه" | "درّب_الشبكة" | "אמן_רשת" | "نیٹ_ٹریننگ" => Value::Number(0.0),
506 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" | "ذخیره_شبکه" | "احفظ_الشبكة" | "שמור_רשת" | "نیٹ_محفوظ_کرو" => {
507 Value::Bool(false)
508 },
509
510 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" | "ساخت_درخت_رفتار" | "ابنِ_شجرة_السلوك" | "בנה_עץ_התנהגות" | "بی_ٹی_تعمیر" => {
512 Value::Number(-1.0)
513 },
514 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" | "تیک_درخت_رفتار" | "نبضة_شجرة_السلوك" | "טיק_עץ_התנהגות" | "بی_ٹی_ٹک" => {
515 Value::Str(String::new())
516 },
517 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" | "وضعیت_درخت_رفتار" | "حالة_شجرة_السلوك" | "סטטוס_עץ_התנהגות" | "بی_ٹی_حالت" => {
518 Value::Number(0.0)
519 },
520 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" | "تنظیم_واقعیت" | "عيّن_حقيقة" | "קבע_עובדה" | "بی_ٹی_سیٹ" => {
521 Value::Unit
522 },
523
524 "dialog_new"
526 | "建对话模型"
527 | "対話モデル作成"
528 | "대화모델생성"
529 | "สร้างโมเดลสนทนา"
530 | "dialog_load_model"
531 | "对话载模"
532 | "対話モデル読込"
533 | "대화모델불러오기"
534 | "โหลดโมเดลสนทนา"
535 | "dialog_train"
536 | "对话训练"
537 | "対話訓練"
538 | "대화훈련"
539 | "ฝึกสนทนา"
540 | "dialog_load"
541 | "对话载入"
542 | "対話読込"
543 | "대화불러오기"
544 | "โหลดชุดสนทนา" | "مدل_گفتگوی_جدید" | "نموذج_حوار_جديد" | "מודל_דיאלוג_חדש" | "نیا_مکالمہ_ماڈل" => Value::Number(-1.0),
545 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" | "بگو" | "قل" | "אמור" | "کہو" => {
546 Value::Str(String::new())
547 },
548 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" | "ذخیره_مدل_گفتگو" | "احفظ_نموذج_الحوار" | "שמור_מודל_דיאלוג" | "مکالمہ_ماڈل_محفوظ" => {
549 Value::Bool(false)
550 },
551 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" | "یادگیری_گفتگو" | "تعلّم_الحوار" | "למד_דיאלוג" | "مکالمہ_سیکھو" => {
552 Value::Unit
553 },
554
555 "net_connect"
557 | "联网"
558 | "ネット接続"
559 | "네트연결"
560 | "เชื่อมเน็ต"
561 | "net_listen"
562 | "监听"
563 | "待機"
564 | "리슨"
565 | "รอรับ"
566 | "net_send"
567 | "发送"
568 | "送信"
569 | "전송"
570 | "ส่ง" | "اتصال_شبکه" | "اتصل_بالشبكة" | "התחבר_לרשת" | "نیٹ_کنیکٹ" => Value::Number(-1.0),
571 "net_recv"
572 | "接收"
573 | "受信"
574 | "수신"
575 | "รับ"
576 | "net_status"
577 | "连接状态"
578 | "接続状態"
579 | "연결상태"
580 | "สถานะการเชื่อม" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" => Value::Str(String::new()),
581 "net_discover" | "发现" | "探索" | "검색" | "ค้นหาเครือข่าย" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" => {
582 Value::List(Vec::new().into())
583 },
584 "net_close"
585 | "断开"
586 | "切断"
587 | "연결종료"
588 | "ตัดเชื่อม"
589 | "net_test"
590 | "测连接"
591 | "接続テスト"
592 | "연결테스트"
593 | "ทดสอบเน็ต" | "بستن_شبکه" | "أغلق_الشبكة" | "סגור_רשת" | "نیٹ_بند" => Value::Number(0.0),
594
595 _ => Value::Unit,
597 })
598}
599
600#[derive(Debug)]
603pub(crate) enum EvalErr {
604 Runtime(String),
605 Return(Value),
606 #[allow(dead_code)] Break,
608}
609
610impl From<String> for EvalErr {
611 fn from(s: String) -> Self {
612 EvalErr::Runtime(s)
613 }
614}
615
616type EvalResult = Result<Value, EvalErr>;
617
618#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
620fn base32_encode(data: &[u8]) -> String {
621 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
622 let mut out = String::new();
623 let mut buffer: u32 = 0;
624 let mut bits = 0u32;
625 for &b in data {
626 buffer = (buffer << 8) | b as u32;
627 bits += 8;
628 while bits >= 5 {
629 bits -= 5;
630 out.push(ALPHABET[((buffer >> bits) & 0x1f) as usize] as char);
631 }
632 }
633 if bits > 0 {
634 out.push(ALPHABET[((buffer << (5 - bits)) & 0x1f) as usize] as char);
635 }
636 out
637}
638
639#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
641fn base32_decode(s: &str) -> Option<Vec<u8>> {
642 let mut buffer: u32 = 0;
643 let mut bits = 0u32;
644 let mut out = Vec::new();
645 for c in s.chars() {
646 if c == '=' || c.is_whitespace() {
647 continue;
648 }
649 let v = match c.to_ascii_uppercase() {
650 'A'..='Z' => c.to_ascii_uppercase() as u32 - 'A' as u32,
651 '2'..='7' => c as u32 - '2' as u32 + 26,
652 _ => return None,
653 };
654 buffer = (buffer << 5) | v;
655 bits += 5;
656 if bits >= 8 {
657 bits -= 8;
658 out.push((buffer >> bits) as u8);
659 }
660 }
661 Some(out)
662}
663
664#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
666fn totp_code(secret_b32: &str, step: u64) -> Option<String> {
667 let key = base32_decode(secret_b32)?;
668 let msg = step.to_be_bytes();
669 let mac = hmac_sha1(&key, &msg);
670 let offset = (mac[19] & 0x0f) as usize;
671 let bin = ((mac[offset] as u32 & 0x7f) << 24)
672 | ((mac[offset + 1] as u32) << 16)
673 | ((mac[offset + 2] as u32) << 8)
674 | (mac[offset + 3] as u32);
675 Some(format!("{:06}", bin % 1_000_000))
676}
677
678#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
680fn totp_check(secret_b32: &str, code: &str) -> bool {
681 if code.len() != 6 || !code.bytes().all(|b| b.is_ascii_digit()) {
682 return false;
683 }
684 let now_step = (crate::runtime::now_secs() as u64) / 30;
685 for delta in [-1i64, 0, 1] {
686 let step = (now_step as i64 + delta) as u64;
687 if let Some(expected) = totp_code(secret_b32, step) {
688 if expected.as_bytes().ct_eq_str(code.as_bytes()) {
690 return true;
691 }
692 }
693 }
694 false
695}
696
697#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
699fn hmac_sha1(key: &[u8], msg: &[u8]) -> [u8; 20] {
700 use hmac::{Mac, SimpleHmac};
701 let mut mac = SimpleHmac::<sha1::Sha1>::new_from_slice(key).expect("hmac key");
702 mac.update(msg);
703 let out = mac.finalize().into_bytes();
704 let mut arr = [0u8; 20];
705 arr.copy_from_slice(&out);
706 arr
707}
708
709#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
711trait CtEqStr {
712 fn ct_eq_str(&self, other: &[u8]) -> bool;
713}
714#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
715impl CtEqStr for [u8] {
716 fn ct_eq_str(&self, other: &[u8]) -> bool {
717 if self.len() != other.len() {
718 return false;
719 }
720 let mut diff = 0u8;
721 for (a, b) in self.iter().zip(other.iter()) {
722 diff |= a ^ b;
723 }
724 diff == 0
725 }
726}
727
728fn url_decode(s: &str) -> String {
730 let bytes = s.as_bytes();
731 let mut out = Vec::with_capacity(bytes.len());
732 let mut i = 0;
733 while i < bytes.len() {
734 match bytes[i] {
735 b'+' => {
736 out.push(b' ');
737 i += 1;
738 },
739 b'%' if i + 2 < bytes.len() => {
740 let hi = (bytes[i + 1] as char).to_digit(16);
741 let lo = (bytes[i + 2] as char).to_digit(16);
742 if let (Some(h), Some(l)) = (hi, lo) {
743 out.push((h * 16 + l) as u8);
744 i += 3;
745 } else {
746 out.push(bytes[i]);
747 i += 1;
748 }
749 },
750 b => {
751 out.push(b);
752 i += 1;
753 },
754 }
755 }
756 String::from_utf8_lossy(&out).into_owned()
757}
758
759#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
761fn values_to_sql_params(args: &[Value]) -> Vec<ling_http::rusqlite::types::Value> {
762 use ling_http::rusqlite::types::Value as Sql;
763 args.iter()
764 .map(|v| match v {
765 Value::Number(n) if n.fract() == 0.0 && n.abs() < 9e15 => Sql::Integer(*n as i64),
766 Value::Number(n) => Sql::Real(*n),
767 Value::Bool(b) => Sql::Integer(*b as i64),
768 other => Sql::Text(other.to_string()),
769 })
770 .collect()
771}
772
773struct SvgWriter {
778 path: String,
779 width: f64,
780 height: f64,
781 elements: Vec<String>,
782}
783
784impl SvgWriter {
785 fn new(path: String, width: f64, height: f64) -> Self {
786 let bg = format!("<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>");
787 Self { path, width, height, elements: vec![bg] }
788 }
789
790 fn save(&self) -> std::io::Result<()> {
791 let w = self.width;
792 let h = self.height;
793 let mut out = format!(
794 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
795 <svg xmlns=\"http://www.w3.org/2000/svg\" \
796 width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
797 );
798 for elem in &self.elements {
799 out.push_str(" ");
800 out.push_str(elem);
801 out.push('\n');
802 }
803 out.push_str("</svg>\n");
804 if let Some(parent) = std::path::Path::new(&self.path).parent() {
806 if !parent.as_os_str().is_empty() {
807 let _ = std::fs::create_dir_all(parent);
808 }
809 }
810 std::fs::write(&self.path, out.as_bytes())
811 }
812}
813
814fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
815 let s = s / 100.0;
816 let l = l / 100.0;
817 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
818 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
819 let m = l - c / 2.0;
820 let (r1, g1, b1) = if h < 60.0 {
821 (c, x, 0.0)
822 } else if h < 120.0 {
823 (x, c, 0.0)
824 } else if h < 180.0 {
825 (0.0, c, x)
826 } else if h < 240.0 {
827 (0.0, x, c)
828 } else if h < 300.0 {
829 (x, 0.0, c)
830 } else {
831 (c, 0.0, x)
832 };
833 let r = ((r1 + m) * 255.0).round() as u8;
834 let g = ((g1 + m) * 255.0).round() as u8;
835 let b = ((b1 + m) * 255.0).round() as u8;
836 format!("#{r:02x}{g:02x}{b:02x}")
837}
838
839fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
842 let mut h = (x as u32)
843 .wrapping_add((y as u32).wrapping_mul(2654435769))
844 .wrapping_add(seed.wrapping_mul(1234567891));
845 h ^= h >> 16;
846 h = h.wrapping_mul(0x45d9f3b);
847 h ^= h >> 16;
848 h as f32 / u32::MAX as f32
849}
850
851fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
852 let xi = x.floor() as i32;
853 let yi = y.floor() as i32;
854 let sm = |t: f32| t * t * (3.0 - 2.0 * t);
855 let xf = sm(x - xi as f32);
856 let yf = sm(y - yi as f32);
857 let a = tex_hash(xi, yi, seed);
858 let b = tex_hash(xi + 1, yi, seed);
859 let c = tex_hash(xi, yi + 1, seed);
860 let d = tex_hash(xi + 1, yi + 1, seed);
861 a + (b - a) * xf + (c - a) * yf + (a - b - c + d) * xf * yf
862}
863
864fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
865 let mut v = 0.0f32;
866 let mut amp = 0.5f32;
867 let mut f = 1.0f32;
868 for i in 0..octaves {
869 v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
870 amp *= 0.5;
871 f *= 2.0;
872 }
873 v
874}
875
876fn tex_palette(name: &str, t: f32) -> [f32; 3] {
877 let (a, b, c, d): ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) = match name {
878 "fire" => (
879 [0.8, 0.4, 0.1],
880 [0.7, 0.3, 0.1],
881 [1.0, 0.5, 0.3],
882 [0.0, 0.5, 0.8],
883 ),
884 "ocean" => (
885 [0.1, 0.4, 0.7],
886 [0.3, 0.3, 0.4],
887 [0.8, 1.0, 0.5],
888 [0.3, 0.0, 0.6],
889 ),
890 "psychedelic" => (
891 [0.5, 0.5, 0.5],
892 [0.8, 0.8, 0.8],
893 [1.0, 1.3, 0.7],
894 [0.0, 0.15, 0.3],
895 ),
896 "neon" => (
897 [0.5, 0.5, 0.5],
898 [0.5, 0.5, 0.5],
899 [2.0, 1.0, 0.0],
900 [0.5, 0.2, 0.25],
901 ),
902 "forest" => (
903 [0.3, 0.5, 0.2],
904 [0.2, 0.3, 0.1],
905 [1.0, 0.5, 0.8],
906 [0.1, 0.3, 0.6],
907 ),
908 _ => (
909 [0.5, 0.5, 0.5],
910 [0.5, 0.5, 0.5],
911 [1.0, 1.0, 1.0],
912 [0.0, 0.333, 0.667],
913 ),
914 };
915 [0, 1, 2]
916 .map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
917}
918
919#[cfg(not(target_arch = "wasm32"))]
921fn key_char(k: minifb::Key, shift: bool) -> Option<char> {
927 use minifb::Key::*;
928 let base = match k {
929 A => 'a',
930 B => 'b',
931 C => 'c',
932 D => 'd',
933 E => 'e',
934 F => 'f',
935 G => 'g',
936 H => 'h',
937 I => 'i',
938 J => 'j',
939 K => 'k',
940 L => 'l',
941 M => 'm',
942 N => 'n',
943 O => 'o',
944 P => 'p',
945 Q => 'q',
946 R => 'r',
947 S => 's',
948 T => 't',
949 U => 'u',
950 V => 'v',
951 W => 'w',
952 X => 'x',
953 Y => 'y',
954 Z => 'z',
955 Key0 => '0',
956 Key1 => '1',
957 Key2 => '2',
958 Key3 => '3',
959 Key4 => '4',
960 Key5 => '5',
961 Key6 => '6',
962 Key7 => '7',
963 Key8 => '8',
964 Key9 => '9',
965 Space => ' ',
966 Minus => '-',
967 Equal => '=',
968 Period => '.',
969 Comma => ',',
970 Slash => '/',
971 Backslash => '\\',
972 Semicolon => ';',
973 Apostrophe => '\'',
974 LeftBracket => '[',
975 RightBracket => ']',
976 Backquote => '`',
977 _ => return None,
978 };
979 if !shift {
980 return Some(base);
981 }
982 Some(match base {
983 'a'..='z' => base.to_ascii_uppercase(),
984 '0' => ')',
985 '1' => '!',
986 '2' => '@',
987 '3' => '#',
988 '4' => '$',
989 '5' => '%',
990 '6' => '^',
991 '7' => '&',
992 '8' => '*',
993 '9' => '(',
994 '-' => '_',
995 '=' => '+',
996 '.' => '>',
997 ',' => '<',
998 '/' => '?',
999 '\\' => '|',
1000 ';' => ':',
1001 '\'' => '"',
1002 '[' => '{',
1003 ']' => '}',
1004 '`' => '~',
1005 other => other,
1006 })
1007}
1008
1009#[cfg(all(not(target_arch = "wasm32"), windows))]
1013fn vk_char(vk: i32, shift: bool) -> Option<char> {
1014 let base = match vk {
1015 0x41..=0x5A => (b'a' + (vk - 0x41) as u8) as char, 0x30..=0x39 => (b'0' + (vk - 0x30) as u8) as char, 0x20 => ' ', 0xBD => '-', 0xBB => '=', 0xBE => '.', 0xBC => ',', 0xBF => '/', 0xDC => '\\', 0xBA => ';', 0xDE => '\'', 0xDB => '[', 0xDD => ']', 0xC0 => '`', _ => return None,
1030 };
1031 if !shift {
1032 return Some(base);
1033 }
1034 Some(match base {
1035 'a'..='z' => base.to_ascii_uppercase(),
1036 '0' => ')',
1037 '1' => '!',
1038 '2' => '@',
1039 '3' => '#',
1040 '4' => '$',
1041 '5' => '%',
1042 '6' => '^',
1043 '7' => '&',
1044 '8' => '*',
1045 '9' => '(',
1046 '-' => '_',
1047 '=' => '+',
1048 '.' => '>',
1049 ',' => '<',
1050 '/' => '?',
1051 '\\' => '|',
1052 ';' => ':',
1053 '\'' => '"',
1054 '[' => '{',
1055 ']' => '}',
1056 '`' => '~',
1057 other => other,
1058 })
1059}
1060
1061#[cfg(all(not(target_arch = "wasm32"), windows))]
1064const TEXT_POLL_VKS: &[i32] = &[
1065 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
1066 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x30, 0x31, 0x32, 0x33,
1067 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0xBD, 0xBB, 0xBE, 0xBC, 0xBF, 0xDC, 0xBA, 0xDE,
1068 0xDB, 0xDD, 0xC0,
1069];
1070#[cfg(all(not(target_arch = "wasm32"), windows))]
1073const VK_BACK: i32 = 0x08;
1074#[cfg(all(not(target_arch = "wasm32"), windows))]
1075const VK_SHIFT: i32 = 0x10;
1076
1077#[cfg(all(not(target_arch = "wasm32"), windows))]
1084fn os_key_down(vk: i32) -> bool {
1085 unsafe {
1086 extern "system" {
1087 fn GetAsyncKeyState(vkey: i32) -> i16;
1088 }
1089 (GetAsyncKeyState(vk) as u16 & 0x8000) != 0
1090 }
1091}
1092
1093#[cfg(all(not(target_arch = "wasm32"), windows))]
1101fn window_is_foreground(hwnd: isize) -> bool {
1102 unsafe {
1103 extern "system" {
1104 fn GetForegroundWindow() -> isize;
1105 }
1106 hwnd != 0 && GetForegroundWindow() == hwnd
1107 }
1108}
1109
1110#[cfg(all(not(target_arch = "wasm32"), windows))]
1113const KEY_REPEAT_DELAY: f64 = 0.45;
1114#[cfg(all(not(target_arch = "wasm32"), windows))]
1116const KEY_REPEAT_RATE: f64 = 0.045;
1117
1118#[cfg(all(not(target_arch = "wasm32"), windows))]
1125fn key_repeat_fire(now: f64, down: bool, was_down: bool, down_since: &mut f64, last_fire: &mut f64) -> bool {
1126 if !down {
1127 return false;
1128 }
1129 if !was_down {
1130 *down_since = now;
1131 *last_fire = now;
1132 return true;
1133 }
1134 if now - *down_since >= KEY_REPEAT_DELAY && now - *last_fire >= KEY_REPEAT_RATE {
1135 *last_fire = now;
1136 return true;
1137 }
1138 false
1139}
1140
1141#[cfg(all(not(target_arch = "wasm32"), windows))]
1145fn str_to_vk(name: &str) -> Option<i32> {
1146 Some(match name {
1147 "numpad0" | "kp0" => 0x60,
1148 "numpad1" | "kp1" => 0x61,
1149 "numpad2" | "kp2" => 0x62,
1150 "numpad3" | "kp3" => 0x63,
1151 "numpad4" | "kp4" => 0x64,
1152 "numpad5" | "kp5" => 0x65,
1153 "numpad6" | "kp6" => 0x66,
1154 "numpad7" | "kp7" => 0x67,
1155 "numpad8" | "kp8" => 0x68,
1156 "numpad9" | "kp9" => 0x69,
1157 "numpad+" | "kp+" => 0x6B,
1158 "numpad-" | "kp-" => 0x6D,
1159 "numpad*" | "kp*" => 0x6A,
1160 "numpad/" | "kp/" => 0x6F,
1161 "left" => 0x25,
1162 "up" => 0x26,
1163 "right" => 0x27,
1164 "down" => 0x28,
1165 "space" => 0x20,
1166 "enter" => 0x0D,
1167 "escape" => 0x1B,
1168 "pageup" => 0x21,
1169 "pagedown" => 0x22,
1170 "lshift" | "leftshift" => 0xA0,
1171 "rshift" | "rightshift" => 0xA1,
1172 "lctrl" | "leftctrl" => 0xA2,
1173 "rctrl" | "rightctrl" => 0xA3,
1174 "lalt" | "leftalt" => 0xA4,
1175 "ralt" | "rightalt" => 0xA5,
1176 "tab" => 0x09,
1177 "backspace" => VK_BACK,
1178 "delete" => 0x2E,
1179 "insert" => 0x2D,
1180 "home" => 0x24,
1181 "end" => 0x23,
1182 "a" => 0x41,
1183 "b" => 0x42,
1184 "c" => 0x43,
1185 "d" => 0x44,
1186 "e" => 0x45,
1187 "f" => 0x46,
1188 "g" => 0x47,
1189 "h" => 0x48,
1190 "i" => 0x49,
1191 "j" => 0x4A,
1192 "k" => 0x4B,
1193 "l" => 0x4C,
1194 "m" => 0x4D,
1195 "n" => 0x4E,
1196 "o" => 0x4F,
1197 "p" => 0x50,
1198 "q" => 0x51,
1199 "r" => 0x52,
1200 "s" => 0x53,
1201 "t" => 0x54,
1202 "u" => 0x55,
1203 "v" => 0x56,
1204 "w" => 0x57,
1205 "x" => 0x58,
1206 "y" => 0x59,
1207 "z" => 0x5A,
1208 "0" => 0x30,
1209 "1" => 0x31,
1210 "2" => 0x32,
1211 "3" => 0x33,
1212 "4" => 0x34,
1213 "5" => 0x35,
1214 "6" => 0x36,
1215 "7" => 0x37,
1216 "8" => 0x38,
1217 "9" => 0x39,
1218 _ => return None,
1219 })
1220}
1221
1222fn hex_encode(bytes: &[u8]) -> String {
1224 let mut s = String::with_capacity(bytes.len() * 2);
1225 for b in bytes {
1226 s.push_str(&format!("{b:02x}"));
1227 }
1228 s
1229}
1230
1231#[cfg(not(target_arch = "wasm32"))]
1233fn decode_blob(s: &str) -> Result<Vec<u8>, String> {
1234 use base64::Engine as _;
1235 use std::io::Read as _;
1236 let comp = base64::engine::general_purpose::STANDARD
1237 .decode(s.trim())
1238 .map_err(|e| format!("base64: {e}"))?;
1239 let mut out = Vec::new();
1240 flate2::read::ZlibDecoder::new(&comp[..])
1241 .read_to_end(&mut out)
1242 .map_err(|e| format!("inflate: {e}"))?;
1243 Ok(out)
1244}
1245
1246#[cfg(not(target_arch = "wasm32"))]
1248fn hex_decode(s: &str) -> Vec<u8> {
1249 let s = s.trim();
1250 (0..s.len() / 2)
1251 .filter_map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
1252 .collect()
1253}
1254
1255#[cfg(not(target_arch = "wasm32"))]
1257fn hex_to_32(s: &str) -> [u8; 32] {
1258 let v = hex_decode(s);
1259 let mut out = [0u8; 32];
1260 let n = v.len().min(32);
1261 out[..n].copy_from_slice(&v[..n]);
1262 out
1263}
1264
1265#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1273fn build_pdf_from_images(paths: &[String], out_path: &str) -> Result<(), Box<dyn std::error::Error>> {
1274 use std::io::Write as _;
1275
1276 struct PageImg {
1277 w: u32,
1278 h: u32,
1279 compressed: Vec<u8>,
1280 }
1281
1282 let mut pages = Vec::with_capacity(paths.len());
1283 for p in paths {
1284 let img = image::open(p)?.to_rgb8();
1285 let (w, h) = (img.width(), img.height());
1286 let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
1287 enc.write_all(img.as_raw())?;
1288 pages.push(PageImg { w, h, compressed: enc.finish()? });
1289 }
1290
1291 let n = pages.len();
1292 let page_nums: Vec<u32> = (0..n).map(|i| 3 + (i as u32) * 3).collect();
1295 let total_objs = 2 + n * 3;
1296 let mut off = vec![0usize; total_objs + 1]; let mut buf: Vec<u8> = Vec::new();
1299 buf.extend_from_slice(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
1300
1301 off[1] = buf.len();
1302 buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1303
1304 off[2] = buf.len();
1305 let kids = page_nums.iter().map(|n| format!("{n} 0 R")).collect::<Vec<_>>().join(" ");
1306 buf.extend_from_slice(format!("2 0 obj\n<< /Type /Pages /Kids [{kids}] /Count {n} >>\nendobj\n").as_bytes());
1307
1308 for (i, page) in pages.iter().enumerate() {
1309 let page_num = page_nums[i];
1310 let content_num = page_num + 1;
1311 let image_num = page_num + 2;
1312 let (w, h) = (page.w, page.h);
1313
1314 off[page_num as usize] = buf.len();
1315 buf.extend_from_slice(format!(
1316 "{page_num} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {w} {h}] /Resources << /XObject << /Im0 {image_num} 0 R >> >> /Contents {content_num} 0 R >>\nendobj\n"
1317 ).as_bytes());
1318
1319 let content = format!("q {w} 0 0 {h} 0 0 cm /Im0 Do Q");
1320 off[content_num as usize] = buf.len();
1321 buf.extend_from_slice(format!(
1322 "{content_num} 0 obj\n<< /Length {} >>\nstream\n{content}\nendstream\nendobj\n",
1323 content.len()
1324 ).as_bytes());
1325
1326 off[image_num as usize] = buf.len();
1327 buf.extend_from_slice(format!(
1328 "{image_num} 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>\nstream\n",
1329 page.compressed.len()
1330 ).as_bytes());
1331 buf.extend_from_slice(&page.compressed);
1332 buf.extend_from_slice(b"\nendstream\nendobj\n");
1333 }
1334
1335 let xref_offset = buf.len();
1336 buf.extend_from_slice(format!("xref\n0 {}\n", total_objs + 1).as_bytes());
1337 buf.extend_from_slice(b"0000000000 65535 f \n");
1338 for entry in off.iter().skip(1) {
1339 buf.extend_from_slice(format!("{entry:010} 00000 n \n").as_bytes());
1340 }
1341 buf.extend_from_slice(
1342 format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF", total_objs + 1).as_bytes(),
1343 );
1344
1345 std::fs::write(out_path, buf)?;
1346 Ok(())
1347}
1348
1349fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
1350 ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
1351}
1352
1353const PERM: [u8; 512] = [
1356 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1357 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1358 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1359 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1360 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1361 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1362 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1363 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1364 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1365 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1366 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180,
1367 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
1368 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
1369 140, 161, 137, 13, 191, 230, 66, 104, 153, 199, 167, 147, 99, 179, 92,
1370 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1372 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1373 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1374 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1375 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1376 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1377 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1378 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1379 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1380 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1381 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174,
1382];
1383
1384fn fade(t: f32) -> f32 {
1385 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1386}
1387
1388fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
1389 let h = hash & 15;
1390 let u = if h < 8 { x } else { y };
1391 let v = if h < 8 { y } else { z };
1392 (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v })
1393}
1394
1395fn perlin3(x: f32, y: f32, z: f32) -> f32 {
1396 let xi = (x.floor() as i32) & 255;
1397 let yi = (y.floor() as i32) & 255;
1398 let zi = (z.floor() as i32) & 255;
1399
1400 let xf = x - x.floor();
1401 let yf = y - y.floor();
1402 let zf = z - z.floor();
1403
1404 let u = fade(xf);
1405 let v = fade(yf);
1406 let w = fade(zf);
1407
1408 let p0 = PERM[xi as usize] as usize;
1409 let p1 = PERM[((xi + 1) & 255) as usize] as usize;
1410 let pa = PERM[(p0 + yi as usize) & 255] as usize;
1411 let pb = PERM[(p0 + ((yi + 1) & 255) as usize) & 255] as usize;
1412 let pc = PERM[(p1 + yi as usize) & 255] as usize;
1413 let pd = PERM[(p1 + ((yi + 1) & 255) as usize) & 255] as usize;
1414
1415 let g000 = grad(PERM[(pa + zi as usize) & 255], xf, yf, zf);
1416 let g001 = grad(
1417 PERM[(pa + ((zi + 1) & 255) as usize) & 255],
1418 xf,
1419 yf,
1420 zf - 1.0,
1421 );
1422 let g010 = grad(PERM[(pb + zi as usize) & 255], xf, yf - 1.0, zf);
1423 let g011 = grad(
1424 PERM[(pb + ((zi + 1) & 255) as usize) & 255],
1425 xf,
1426 yf - 1.0,
1427 zf - 1.0,
1428 );
1429 let g100 = grad(PERM[(pc + zi as usize) & 255], xf - 1.0, yf, zf);
1430 let g101 = grad(
1431 PERM[(pc + ((zi + 1) & 255) as usize) & 255],
1432 xf - 1.0,
1433 yf,
1434 zf - 1.0,
1435 );
1436 let g110 = grad(PERM[(pd + zi as usize) & 255], xf - 1.0, yf - 1.0, zf);
1437 let g111 = grad(
1438 PERM[(pd + ((zi + 1) & 255) as usize) & 255],
1439 xf - 1.0,
1440 yf - 1.0,
1441 zf - 1.0,
1442 );
1443
1444 let l00 = g000 + u * (g100 - g000);
1445 let l01 = g001 + u * (g101 - g001);
1446 let l10 = g010 + u * (g110 - g010);
1447 let l11 = g011 + u * (g111 - g011);
1448
1449 let l0 = l00 + v * (l10 - l00);
1450 let l1 = l01 + v * (l11 - l01);
1451
1452 l0 + w * (l1 - l0)
1453}
1454
1455fn fast_rand_f64(state: &mut u64) -> f64 {
1456 *state = state
1457 .wrapping_mul(6364136223846793005)
1458 .wrapping_add(1442695040888963407);
1459 ((*state >> 32) as u32) as f64 / 4294967296.0
1460}
1461
1462#[inline]
1466fn put_px(buf: &mut [u32], idx: usize, color: u32, blend: u8) {
1467 if idx >= buf.len() {
1468 return;
1469 }
1470 if blend == 0 {
1471 buf[idx] = color;
1472 } else {
1473 let old = buf[idx];
1474 let r = (((old >> 16) & 255) + ((color >> 16) & 255)).min(255);
1475 let g = (((old >> 8) & 255) + ((color >> 8) & 255)).min(255);
1476 let b = ((old & 255) + (color & 255)).min(255);
1477 buf[idx] = (r << 16) | (g << 8) | b;
1478 }
1479}
1480
1481#[inline]
1483fn rgb(r: f64, g: f64, b: f64) -> u32 {
1484 let r = (r as i64).clamp(0, 255) as u32;
1485 let g = (g as i64).clamp(0, 255) as u32;
1486 let b = (b as i64).clamp(0, 255) as u32;
1487 (r << 16) | (g << 8) | b
1488}
1489
1490#[allow(clippy::too_many_arguments)]
1491fn draw_circle_outline(
1492 buf: &mut [u32],
1493 w: i32,
1494 h: i32,
1495 cx: i32,
1496 cy: i32,
1497 r: i32,
1498 color: u32,
1499 blend: u8,
1500) {
1501 let r = r.clamp(0, 20000); if r == 0 {
1503 return;
1504 }
1505 let mut x = 0;
1506 let mut y = r;
1507 let mut d = 3 - 2 * r;
1508 while x <= y {
1509 plot_circle_points(buf, w, h, cx, cy, x, y, color, blend);
1510 if d < 0 {
1511 d += 4 * x + 6;
1512 } else {
1513 d += 4 * (x - y) + 10;
1514 y -= 1;
1515 }
1516 x += 1;
1517 }
1518}
1519
1520#[allow(clippy::too_many_arguments)]
1521fn plot_circle_points(
1522 buf: &mut [u32],
1523 w: i32,
1524 h: i32,
1525 cx: i32,
1526 cy: i32,
1527 x: i32,
1528 y: i32,
1529 color: u32,
1530 blend: u8,
1531) {
1532 let points = [
1533 (cx + x, cy + y),
1534 (cx - x, cy + y),
1535 (cx + x, cy - y),
1536 (cx - x, cy - y),
1537 (cx + y, cy + x),
1538 (cx - y, cy + x),
1539 (cx + y, cy - x),
1540 (cx - y, cy - x),
1541 ];
1542 for &(px, py) in &points {
1543 if px >= 0 && px < w && py >= 0 && py < h {
1544 put_px(buf, (py * w + px) as usize, color, blend);
1545 }
1546 }
1547}
1548
1549#[allow(clippy::too_many_arguments)]
1550fn draw_circle_filled(
1551 buf: &mut [u32],
1552 w: i32,
1553 h: i32,
1554 cx: i32,
1555 cy: i32,
1556 r: i32,
1557 color: u32,
1558 blend: u8,
1559) {
1560 if r <= 0 {
1561 return;
1562 }
1563 for dy in -r..=r {
1564 let dx_max = ((r * r - dy * dy) as f64).sqrt() as i32;
1565 let py = cy + dy;
1566 if py < 0 || py >= h {
1567 continue;
1568 }
1569 for dx in -dx_max..=dx_max {
1570 let px = cx + dx;
1571 if px >= 0 && px < w {
1572 put_px(buf, (py * w + px) as usize, color, blend);
1573 }
1574 }
1575 }
1576}
1577
1578#[cfg(test)]
1579mod draw_tests {
1580 use super::*;
1581
1582 #[test]
1583 fn filled_circle_actually_writes_pixels() {
1584 let mut buf = vec![0u32; 100 * 100];
1585 draw_circle_filled(&mut buf, 100, 100, 50, 50, 10, 0xFF00FF, 0);
1586 assert_eq!(buf[50 * 100 + 50], 0xFF00FF, "centre pixel must be filled");
1587 assert_eq!(buf[0], 0, "far corner must stay clear");
1588 let n = buf.iter().filter(|&&p| p != 0).count();
1589 assert!(n > 200 && n < 500, "r=10 disc area ≈ 314, got {n}");
1590 }
1591
1592 #[test]
1593 fn circle_outline_writes_a_ring() {
1594 let mut buf = vec![0u32; 100 * 100];
1595 draw_circle_outline(&mut buf, 100, 100, 50, 50, 20, 0x00FF00, 0);
1596 assert_eq!(buf[50 * 100 + 50], 0, "outline must NOT fill the centre");
1597 assert!(
1598 buf.iter().any(|&p| p == 0x00FF00),
1599 "outline must draw a ring"
1600 );
1601 }
1602
1603 #[test]
1604 fn additive_blend_accumulates_channels() {
1605 let mut buf = vec![0x202020u32; 1];
1606 put_px(&mut buf, 0, 0x404040, 1);
1607 assert_eq!(buf[0], 0x606060);
1608 }
1609}
1610
1611#[derive(Clone, Copy)]
1617pub struct UiTheme {
1618 pub primary: u32,
1619 pub accent: u32,
1620 pub track: u32,
1621 pub warn: u32,
1622 pub text: u32,
1623 pub bg: u32,
1624}
1625
1626impl Default for UiTheme {
1627 fn default() -> Self {
1628 Self {
1629 primary: 0x00D2FF, accent: 0x28FFB4, track: 0x2C3E64, warn: 0xFF5A5A, text: 0xBEEBFF, bg: 0x0A1018, }
1636 }
1637}
1638
1639#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
1640pub struct Interpreter {
1641 globals: HashMap<String, Expr>,
1642 global_seed: Env,
1645 functions: FxHashMap<String, Rc<FnDef>>,
1646 pub(crate) structs: HashMap<String, Vec<String>>,
1648 enum_variants: HashMap<String, (String, usize)>,
1650 _modules: HashMap<String, Vec<FnDef>>,
1651 gfx: RefCell<GfxState>,
1652 svg: RefCell<Option<SvgWriter>>,
1653 pub source_dir: Option<std::path::PathBuf>,
1655 loaded_files: std::collections::HashSet<String>,
1657 #[cfg(not(target_arch = "wasm32"))]
1659 audio: Option<AudioEngine>,
1660 #[cfg(not(target_arch = "wasm32"))]
1661 fft: RefCell<FftAnalyzer>,
1662 fft_bands_cache: RefCell<Vec<f32>>,
1663 start_time_secs: f64,
1666 frame_num: u64,
1668 #[cfg(target_arch = "wasm32")]
1670 wasm_target_fps: f64,
1671 #[cfg(target_arch = "wasm32")]
1673 wasm_next_present_ms: f64,
1674 rand_state: u64,
1676 #[cfg(not(target_arch = "wasm32"))]
1678 mic: Option<ling_mic::MicInput>,
1679 #[cfg(not(target_arch = "wasm32"))]
1681 crypto_ids: Vec<ling_crypto::KnotIdentity>,
1682 #[cfg(not(target_arch = "wasm32"))]
1684 ed25519_ids: Vec<ling_crypto::Ed25519Keypair>,
1685 #[cfg(not(target_arch = "wasm32"))]
1687 mldsa_ids: Vec<ling_crypto::MlDsa65Keypair>,
1688 text_buffer: String,
1690 record_n: u32,
1692 #[cfg(not(target_arch = "wasm32"))]
1694 mic_buffer: Vec<f32>,
1695 #[cfg(not(target_arch = "wasm32"))]
1697 fonts: Vec<ling_graphics::VectorFont>,
1698 images: Vec<image::RgbaImage>,
1701 ui_theme: UiTheme,
1703 mouse_was_down: bool,
1705 #[cfg(not(target_arch = "wasm32"))]
1707 music: Option<ling_music::MusicEngine>,
1708 #[cfg(not(target_arch = "wasm32"))]
1709 music_init: bool,
1710 tracks: Vec<ling_music::DecodedAudio>,
1712 lyrics: Vec<ling_music::Lyrics>,
1714 midis: Vec<ling_music::MidiSong>,
1716 soft_bodies: Vec<ling_physics::soft::SoftBody>,
1718 rigid_world: ling_physics::rigid::PhysicsWorld,
1720 liquids: Vec<ling_physics::liquid::LiquidGrid>,
1722 meshes: Vec<crate::gfx::shapes::ColorMesh>,
1723 gltf_models: std::cell::RefCell<Vec<ling_physics::gltf::GltfModel>>,
1725 dialog: Option<ling_game::dialog::Dialog>,
1727 dialog_colors: [u32; 4],
1729 frames: Vec<String>,
1731 error_trace: Option<Vec<String>>,
1734 #[cfg(not(target_arch = "wasm32"))]
1738 input: RefCell<Option<InputState>>,
1739 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1744 http_routes: Vec<(String, String, Value)>,
1745 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1749 db: Option<ling_http::rusqlite::Connection>,
1750 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1753 http_static_dirs: Vec<(String, String)>,
1754 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1758 async_jobs: web::AsyncJobs,
1759}
1760
1761#[cfg(not(target_arch = "wasm32"))]
1763struct InputState {
1764 sensorium: ling_input::Sensorium,
1765 backend: ling_input::backend::GilrsBackend,
1766}
1767
1768impl Default for Interpreter {
1769 fn default() -> Self {
1770 Self::new()
1771 }
1772}
1773
1774impl Interpreter {
1775 pub fn new() -> Self {
1776 #[cfg(not(target_arch = "wasm32"))]
1777 let audio = AudioEngine::new()
1778 .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
1779 .ok();
1780 Self {
1781 globals: HashMap::new(),
1782 global_seed: new_env(),
1783 functions: FxHashMap::default(),
1784 structs: HashMap::new(),
1785 enum_variants: HashMap::new(),
1786 _modules: HashMap::new(),
1787 gfx: RefCell::new(GfxState::new()),
1788 svg: RefCell::new(None),
1789 source_dir: None,
1790 loaded_files: std::collections::HashSet::new(),
1791 #[cfg(not(target_arch = "wasm32"))]
1792 audio,
1793 #[cfg(not(target_arch = "wasm32"))]
1794 fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
1795 fft_bands_cache: RefCell::new(vec![]),
1796 start_time_secs: crate::runtime::now_secs(),
1797 frame_num: 0,
1798 #[cfg(target_arch = "wasm32")]
1799 wasm_target_fps: 60.0,
1800 #[cfg(target_arch = "wasm32")]
1801 wasm_next_present_ms: 0.0,
1802 rand_state: 0x123456789ABCDEF,
1803 #[cfg(not(target_arch = "wasm32"))]
1804 mic: None,
1805 #[cfg(not(target_arch = "wasm32"))]
1806 crypto_ids: Vec::new(),
1807 #[cfg(not(target_arch = "wasm32"))]
1808 ed25519_ids: Vec::new(),
1809 #[cfg(not(target_arch = "wasm32"))]
1810 mldsa_ids: Vec::new(),
1811 text_buffer: String::new(),
1812 record_n: 0,
1813 #[cfg(not(target_arch = "wasm32"))]
1814 mic_buffer: Vec::new(),
1815 #[cfg(not(target_arch = "wasm32"))]
1816 fonts: Vec::new(),
1817 images: Vec::new(),
1818 ui_theme: UiTheme::default(),
1819 mouse_was_down: false,
1820 #[cfg(not(target_arch = "wasm32"))]
1821 music: None,
1822 #[cfg(not(target_arch = "wasm32"))]
1823 music_init: false,
1824 tracks: Vec::new(),
1825 lyrics: Vec::new(),
1826 midis: Vec::new(),
1827 soft_bodies: Vec::new(),
1828 rigid_world: ling_physics::rigid::PhysicsWorld::new(),
1829 liquids: Vec::new(),
1830 meshes: Vec::new(),
1831 gltf_models: std::cell::RefCell::new(Vec::new()),
1832 dialog: None,
1833 dialog_colors: [0xE6F2FF, 0xFFD24A, 0x4AD2FF, 0x6CFF8C], frames: Vec::new(),
1835 error_trace: None,
1836 #[cfg(not(target_arch = "wasm32"))]
1837 input: RefCell::new(None),
1838 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1839 http_routes: Vec::new(),
1840 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1841 db: None,
1842 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1843 http_static_dirs: Vec::new(),
1844 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1845 async_jobs: web::AsyncJobs::new(),
1846 }
1847 }
1848
1849 #[cfg(not(target_arch = "wasm32"))]
1853 fn pad_poll(&self) -> usize {
1854 let mut slot = self.input.borrow_mut();
1855 if slot.is_none() {
1856 match ling_input::backend::GilrsBackend::new() {
1857 Ok(backend) => {
1858 *slot = Some(InputState { sensorium: ling_input::Sensorium::new(4), backend });
1859 },
1860 Err(_) => return 0,
1861 }
1862 }
1863 let st = slot.as_mut().unwrap();
1864 st.sensorium.begin_frame();
1865 st.sensorium.pump(&mut st.backend);
1866 st.sensorium.update(1.0 / 60.0);
1867 st.sensorium.devices.count()
1868 }
1869
1870 #[cfg(not(target_arch = "wasm32"))]
1873 fn with_pad<T>(&self, slot: usize, default: T, f: impl FnOnce(&ling_input::Gamepad) -> T) -> T {
1874 let inp = self.input.borrow();
1875 match inp.as_ref().and_then(|s| s.sensorium.player(slot)) {
1876 Some(p) => f(p),
1877 None => default,
1878 }
1879 }
1880
1881 pub fn take_error_trace(&mut self) -> Vec<String> {
1884 self.error_trace.take().unwrap_or_default()
1885 }
1886
1887 #[cfg(target_arch = "wasm32")]
1888 fn wasm_pace_frame(&mut self) {
1889 let fps = self.wasm_target_fps.max(1.0);
1890 let frame_ms = 1000.0 / fps;
1891 let now = js_sys::Date::now();
1892 if self.wasm_next_present_ms <= 0.0 {
1893 self.wasm_next_present_ms = now + frame_ms;
1894 return;
1895 }
1896
1897 let wait_ms = (self.wasm_next_present_ms - now).floor() as i32;
1898 if wait_ms > 0 {
1899 wasm_sleep_ms(wait_ms);
1900 }
1901
1902 let after = js_sys::Date::now();
1903 if after > self.wasm_next_present_ms + frame_ms * 3.0 {
1904 self.wasm_next_present_ms = after + frame_ms;
1905 } else {
1906 self.wasm_next_present_ms += frame_ms;
1907 }
1908 }
1909
1910 fn framed<T, F>(&mut self, name: &str, body: F) -> Result<T, EvalErr>
1913 where
1914 F: FnOnce(&mut Self) -> Result<T, EvalErr>,
1915 {
1916 self.frames.push(name.to_string());
1917 let result = body(self);
1918 if matches!(result, Err(EvalErr::Runtime(_))) && self.error_trace.is_none() {
1919 self.error_trace = Some(self.frames.clone());
1920 }
1921 self.frames.pop();
1922 result
1923 }
1924
1925 #[cfg(not(target_arch = "wasm32"))]
1929 fn render_dialog(&mut self, x: f32, y: f32, w: f32, h: f32, font: i64, t: f32) {
1930 let (runs, typing) = match &self.dialog {
1931 Some(d) if !d.is_closed() => {
1932 let runs: Vec<(String, usize, bool)> = d
1933 .visible_runs()
1934 .into_iter()
1935 .map(|r| (r.text, r.role.index(), r.newline_before))
1936 .collect();
1937 (runs, d.is_typing())
1938 },
1939 _ => return,
1940 };
1941 let colors = self.dialog_colors;
1942 let b = 12.0;
1944 let corners: Vec<[f32; 2]> = vec![
1945 [x + b, y],
1946 [x + w - b, y],
1947 [x + w, y + b],
1948 [x + w, y + h - b],
1949 [x + w - b, y + h],
1950 [x + b, y + h],
1951 [x, y + h - b],
1952 [x, y + b],
1953 [x + b, y],
1954 ];
1955 {
1956 let mut gfx = self.gfx.borrow_mut();
1957 let (bw, bh) = (gfx.width, gfx.height);
1958 crate::gfx::raster::fill_contours_aa(
1959 &mut gfx.buffer,
1960 bw,
1961 bh,
1962 0x0A1018,
1963 false,
1964 std::slice::from_ref(&corners),
1965 );
1966 for seg in corners.windows(2) {
1967 crate::gfx::raster::draw_line_aa(
1968 &mut gfx.buffer,
1969 bw,
1970 bh,
1971 0x00D2FF,
1972 false,
1973 seg[0][0],
1974 seg[0][1],
1975 seg[1][0],
1976 seg[1][1],
1977 );
1978 }
1979 }
1980 let px = 22.0f32;
1982 let pad = 20.0f32;
1983 let line_h = px * 1.45;
1984 let mut cx = x + pad;
1985 let mut cy = y + pad;
1986 let use_font = font >= 0 && (font as usize) < self.fonts.len();
1987 for (text, role, nl) in &runs {
1988 if *nl {
1989 cx = x + pad;
1990 cy += line_h;
1991 }
1992 for word in text.split_inclusive(' ') {
1993 let wpx = if use_font {
1994 self.fonts[font as usize].measure(word, px)
1995 } else {
1996 ling_ui::holo::text_width(word, px * 0.6, px * 0.24)
1997 };
1998 if cx + wpx > x + w - pad && cx > x + pad + 1.0 {
1999 cx = x + pad;
2000 cy += line_h;
2001 }
2002 if cy + line_h > y + h {
2003 break;
2004 }
2005 let col = colors[(*role).min(3)];
2006 if use_font {
2007 let glyphs = self.font_layout_2d_glyphs(font as usize, cx, cy, px, word);
2008 let mut gfx = self.gfx.borrow_mut();
2009 let (bw, bh, add) = (gfx.width, gfx.height, gfx.blend == 1);
2010 for contours in &glyphs {
2011 crate::gfx::raster::fill_contours_aa(
2012 &mut gfx.buffer,
2013 bw,
2014 bh,
2015 col,
2016 add,
2017 contours,
2018 );
2019 }
2020 } else {
2021 let segs = ling_ui::holo::text_lines(word, cx, cy, px * 0.6, px, px * 0.24);
2022 let mut gfx = self.gfx.borrow_mut();
2023 let (bw, bh) = (gfx.width, gfx.height);
2024 for s in segs {
2025 draw_line(&mut gfx.buffer, bw, bh, col, s[0], s[1], s[2], s[3]);
2026 }
2027 }
2028 cx += wpx;
2029 }
2030 }
2031 if !typing && (t * 3.0).sin() > 0.0 {
2033 let ax = x + w - 26.0;
2034 let ay = y + h - 22.0;
2035 let mut gfx = self.gfx.borrow_mut();
2036 let (bw, bh) = (gfx.width, gfx.height);
2037 crate::gfx::raster::fill_contours_aa(
2038 &mut gfx.buffer,
2039 bw,
2040 bh,
2041 0x00D2FF,
2042 false,
2043 std::slice::from_ref(&vec![
2044 [ax - 7.0, ay],
2045 [ax + 7.0, ay],
2046 [ax, ay + 9.0],
2047 [ax - 7.0, ay],
2048 ]),
2049 );
2050 }
2051 }
2052
2053 #[cfg(not(target_arch = "wasm32"))]
2056 fn ensure_music(&mut self) -> bool {
2057 if self.music.is_some() {
2058 return true;
2059 }
2060 if self.music_init {
2061 return false;
2062 }
2063 self.music_init = true;
2064 match ling_music::MusicEngine::new() {
2065 Ok(m) => {
2066 self.music = Some(m);
2067 true
2068 },
2069 Err(e) => {
2070 eprintln!("music engine init failed (no music playback): {e}");
2071 false
2072 },
2073 }
2074 }
2075
2076 #[cfg(target_arch = "wasm32")]
2077 fn wasm_resolve_source_path(&self, path: &str) -> String {
2078 let p = path.trim();
2079 if p.is_empty() {
2080 return String::new();
2081 }
2082 if p.contains("://") || p.starts_with('/') || p.starts_with("./") || p.starts_with("../") {
2083 return p.to_string();
2084 }
2085 if let Some(d) = &self.source_dir {
2086 let base = d.to_string_lossy().replace('\\', "/");
2087 if !base.is_empty() {
2088 return format!(
2089 "{}/{}",
2090 base.trim_end_matches('/'),
2091 p.trim_start_matches("./")
2092 );
2093 }
2094 }
2095 p.to_string()
2096 }
2097
2098 #[cfg(target_arch = "wasm32")]
2099 fn wasm_music_builtin(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, EvalErr> {
2100 match name {
2101 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" | "charger_musique" | "musik_laden" | "загрузить_музыку" =>
2103 {
2104 let path = self.arg_str(args, 0, "");
2105 let resolved = self.wasm_resolve_source_path(&path);
2106 match wasm_fetch_bytes(&resolved)
2107 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
2108 {
2109 Ok(t) => {
2110 let id = self.tracks.len();
2111 self.tracks.push(t);
2112 return Ok(Some(Value::Number(id as f64)));
2113 },
2114 Err(e) => {
2115 eprintln!("music_load failed ({path}): {e}");
2116 return Ok(Some(Value::Number(-1.0)));
2117 },
2118 }
2119 },
2120 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" | "durée_musique" | "musik_dauer" | "длительность_музыки" =>
2121 {
2122 let id = self.arg_num(args, 0, 0.0)? as i64;
2123 let d = self
2124 .tracks
2125 .get(id as usize)
2126 .map(|t| t.duration)
2127 .unwrap_or(0.0);
2128 return Ok(Some(Value::Number(d as f64)));
2129 },
2130 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" | "ضربان_در_دقیقه" | "نبضات_بالدقيقة" | "פעימות_לדקה" | "بی_پی_ایم" | "bpm_musique" | "musik_bpm" | "музыка_bpm" =>
2131 {
2132 let id = self.arg_num(args, 0, 0.0)? as i64;
2133 let b = self
2134 .tracks
2135 .get(id as usize)
2136 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
2137 .unwrap_or(0.0);
2138 return Ok(Some(Value::Number(b as f64)));
2139 },
2140 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" | "tonalité_musique" | "musik_tonart" | "тональность_музыки" => {
2141 let id = self.arg_num(args, 0, 0.0)? as i64;
2142 let k = self
2143 .tracks
2144 .get(id as usize)
2145 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
2146 .unwrap_or_default();
2147 return Ok(Some(Value::Str(k)));
2148 },
2149 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" | "attaques_musique" | "musik_einsätze" | "атаки_музыки" =>
2150 {
2151 let id = self.arg_num(args, 0, 0.0)? as i64;
2152 let v = self
2153 .tracks
2154 .get(id as usize)
2155 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
2156 .unwrap_or_default();
2157 return Ok(Some(Value::List(
2158 v.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2159 )));
2160 },
2161 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" | "شبکه_ضرب" | "شبكة_الإيقاع" | "רשת_פעימות" | "بیٹ_گرڈ" | "grille_temps_musique" | "musik_taktraster" | "сетка_ритма_музыки" =>
2162 {
2163 let id = self.arg_num(args, 0, 0.0)? as i64;
2164 let beats = self
2165 .tracks
2166 .get(id as usize)
2167 .map(|t| {
2168 let b = ling_music::analysis::bpm(&t.mono, t.rate);
2169 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
2170 })
2171 .unwrap_or_default();
2172 return Ok(Some(Value::List(
2173 beats.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2174 )));
2175 },
2176 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" | "بارگذاری_متن_ترانه" | "تحميل_كلمات_الأغنية" | "טעינת_מילות_שיר" | "گیت_متن_لوڈ" | "lrc_musique" | "musik_lrc" | "lrc_музыки" =>
2177 {
2178 let path = self.arg_str(args, 0, "");
2179 let resolved = self.wasm_resolve_source_path(&path);
2180 match wasm_fetch_text(&resolved) {
2181 Ok(text) => {
2182 let id = self.lyrics.len();
2183 self.lyrics.push(ling_music::Lyrics::parse(&text));
2184 return Ok(Some(Value::Number(id as f64)));
2185 },
2186 Err(e) => {
2187 eprintln!("music_lrc failed ({path}): {e}");
2188 return Ok(Some(Value::Number(-1.0)));
2189 },
2190 }
2191 },
2192 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" | "متن_ترانه_فعلی" | "كلمات_الأغنية_الحالية" | "מילות_שיר_נוכחיות" | "موجودہ_گیت_متن" | "paroles_musique" | "musik_liedtext" | "текст_песни" =>
2193 {
2194 let id = self.arg_num(args, 0, 0.0)? as i64;
2195 let t = self.arg_num(args, 1, 0.0)? as f32;
2196 let line = self
2197 .lyrics
2198 .get(id as usize)
2199 .map(|l| l.line_at(t).to_string())
2200 .unwrap_or_default();
2201 return Ok(Some(Value::Str(line)));
2202 },
2203 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" | "بارگذاری_MIDI" | "تحميل_MIDI" | "טעינת_MIDI" | "MIDI_لوڈ" | "charger_midi_musique" | "musik_midi_laden" | "загрузить_midi_музыки" =>
2204 {
2205 let path = self.arg_str(args, 0, "");
2206 let resolved = self.wasm_resolve_source_path(&path);
2207 match wasm_fetch_bytes(&resolved).and_then(|bytes| {
2208 ling_music::midi::from_bytes(&bytes).map_err(|e| e.to_string())
2209 }) {
2210 Ok(m) => {
2211 let id = self.midis.len();
2212 self.midis.push(m);
2213 return Ok(Some(Value::Number(id as f64)));
2214 },
2215 Err(e) => {
2216 eprintln!("music_midi_load failed ({path}): {e}");
2217 return Ok(Some(Value::Number(-1.0)));
2218 },
2219 }
2220 },
2221 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" | "تعداد_MIDI" | "عدد_MIDI" | "מספר_MIDI" | "MIDI_تعداد" | "nombre_midi_musique" | "musik_midi_anzahl" | "число_midi_музыки" =>
2222 {
2223 let id = self.arg_num(args, 0, 0.0)? as i64;
2224 let n = self
2225 .midis
2226 .get(id as usize)
2227 .map(|m| m.notes.len())
2228 .unwrap_or(0);
2229 return Ok(Some(Value::Number(n as f64)));
2230 },
2231 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" | "نتهای_MIDI" | "نغمات_MIDI" | "תווי_MIDI" | "MIDI_نوٹس" | "notes_midi_musique" | "musik_midi_noten" | "ноты_midi_музыки" =>
2232 {
2233 let id = self.arg_num(args, 0, 0.0)? as i64;
2234 let mut out = Vec::new();
2235 if let Some(m) = self.midis.get(id as usize) {
2236 for n in &m.notes {
2237 out.push(Value::Number(n.time as f64));
2238 out.push(Value::Number(n.midi as f64));
2239 }
2240 }
2241 return Ok(Some(Value::List(out.into())));
2242 },
2243 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" | "میلههای_MIDI" | "أعمدة_MIDI" | "עמודות_MIDI" | "MIDI_بارز" | "mesures_midi_musique" | "musik_midi_takte" | "такты_midi_музыки" =>
2244 {
2245 let id = self.arg_num(args, 0, 0.0)? as i64;
2246 let mut out = Vec::new();
2247 if let Some(m) = self.midis.get(id as usize) {
2248 for n in &m.notes {
2249 out.push(Value::Number(n.time as f64));
2250 out.push(Value::Number(n.midi as f64));
2251 out.push(Value::Number(n.dur as f64));
2252 }
2253 }
2254 return Ok(Some(Value::List(out.into())));
2255 },
2256 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" | "داوری_ضرب" | "حكم_الإيقاع" | "שיפוט_קצב" | "بیٹ_فیصلہ" | "juger_musique" | "musik_bewerten" | "оценить_музыку" =>
2257 {
2258 let delta_ms = self.arg_num(args, 0, 9999.0)? as f32;
2259 return Ok(Some(Value::Number(
2260 ling_music::Grade::judge(delta_ms).index() as f64,
2261 )));
2262 },
2263 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" | "نام_رتبه" | "اسم_التقييم" | "שם_דירוג" | "گریڈ_نام" | "nom_grade_musique" | "musik_bewertungsname" | "имя_оценки_музыки" =>
2264 {
2265 let idx = self.arg_num(args, 0, 4.0)? as i32;
2266 return Ok(Some(Value::Str(
2267 ling_music::Grade::from_index(idx).name().to_string(),
2268 )));
2269 },
2270 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" | "نام_نت" | "اسم_النغمة" | "שם_תו" | "نوٹ_نام" | "nom_note_musique" | "musik_notenname" | "имя_ноты_музыки" =>
2271 {
2272 let hz = self.arg_num(args, 0, 0.0)? as f32;
2273 return Ok(Some(Value::Str(ling_music::note::hz_to_name(hz))));
2274 },
2275 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" | "فرکانس_نت" | "تردد_النغمة" | "תדר_תו" | "نوٹ_ہرٹز" | "hz_musique" | "musik_hz" | "музыка_гц" =>
2276 {
2277 let midi = match args.get(0) {
2278 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(69),
2279 Some(Value::Number(n)) => *n as i32,
2280 _ => 69,
2281 };
2282 return Ok(Some(Value::Number(
2283 ling_music::note::midi_to_hz(midi as f32) as f64,
2284 )));
2285 },
2286 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" | "امتیاز_زیروبمی" | "درجة_طبقة_الصوت" | "ציון_גובה_צליל" | "پچ_اسکور" | "score_hauteur_musique" | "musik_tonhöhen_punktzahl" | "счёт_высоты_тона" =>
2287 {
2288 let hz = self.arg_num(args, 0, 0.0)? as f32;
2289 let target = self.arg_num(args, 1, 0.0)? as f32;
2290 return Ok(Some(Value::Number(
2291 ling_music::karaoke::pitch_score(hz, target) as f64,
2292 )));
2293 },
2294
2295 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" | "jouer_musique" | "musik_abspielen" | "играть_музыку" =>
2297 {
2298 let id = self.arg_num(args, 0, 0.0)? as usize;
2299 if let Some(t) = self.tracks.get(id) {
2300 crate::gfx::audio_web::play_music(id, &t.stereo, t.channels, t.rate, 1.0);
2301 }
2302 return Ok(Some(Value::Unit));
2303 },
2304 "music_pause"
2305 | "暂停音乐"
2306 | "音楽一時停止"
2307 | "음악일시정지"
2308 | "หยุดเพลงชั่วคราว"
2309 | "music_stop"
2310 | "停止音乐"
2311 | "音楽停止"
2312 | "음악정지"
2313 | "หยุดเพลง" | "مکث_موسیقی" | "ألبث_الموسيقى" | "השהה_מוזיקה" | "موسیقی_روکو_مؤقت" | "pause_musique" | "musik_pausieren" | "пауза_музыки" => {
2314 let id = self.arg_num(args, 0, 0.0)? as usize;
2315 crate::gfx::audio_web::stop_music(id);
2316 return Ok(Some(Value::Unit));
2317 },
2318 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" | "جستجوی_موسیقی" | "ابحث_في_الموسيقى" | "חפש_במוזיקה" | "موسیقی_تلاش" | "chercher_musique" | "musik_suchen" | "перемотать_музыку" =>
2319 {
2320 return Ok(Some(Value::Unit));
2322 },
2323 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" | "موقعیت_موسیقی" | "موضع_الموسيقى" | "מיקום_מוזיקה" | "موسیقی_مقام" | "position_musique" | "musik_position" | "позиция_музыки" =>
2324 {
2325 return Ok(Some(Value::Number(
2326 crate::gfx::audio_web::current_music_position(),
2327 )));
2328 },
2329 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" | "بلندی_موسیقی" | "مستوى_الموسيقى" | "עוצמת_מוזיקה" | "موسیقی_شدت" | "volume_musique" | "musik_lautstärke" | "громкость_музыки" =>
2330 {
2331 let vol = self.arg_num(args, 0, 0.8)? as f32;
2332 crate::gfx::audio_web::set_music_volume(0, vol);
2334 return Ok(Some(Value::Unit));
2335 },
2336
2337 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" | "طیف_موسیقی" | "طيف_الموسيقى" | "ספקטרום_מוזיקה" | "میوزک_اسپیکٹرم" | "fft_musique" | "musik_fft" | "fft_музыки" =>
2339 {
2340 let id = self.arg_num(args, 0, 0.0)? as usize;
2341 let nbands = self.arg_num(args, 1, 16.0)? as usize;
2342 let pos = crate::gfx::audio_web::current_music_position() as f32;
2343 let bands = if let Some(t) = self.tracks.get(id) {
2344 ling_music::analysis::fft_bands_at_pos(&t.mono, t.rate, pos, nbands)
2345 } else {
2346 vec![0.0f32; nbands]
2347 };
2348 return Ok(Some(Value::List(
2349 bands.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2350 )));
2351 },
2352
2353 _ => {},
2354 }
2355 Ok(None)
2356 }
2357
2358 #[cfg(not(target_arch = "wasm32"))]
2362 fn font_layout_2d(
2363 &mut self,
2364 id: usize,
2365 x: f32,
2366 y: f32,
2367 px: f32,
2368 text: &str,
2369 ) -> Vec<Vec<[f32; 2]>> {
2370 let mut out = Vec::new();
2371 for g in self.font_layout_2d_glyphs(id, x, y, px, text) {
2372 out.extend(g);
2373 }
2374 out
2375 }
2376
2377 #[cfg(not(target_arch = "wasm32"))]
2380 fn font_layout_2d_glyphs(
2381 &mut self,
2382 id: usize,
2383 x: f32,
2384 y: f32,
2385 px: f32,
2386 text: &str,
2387 ) -> Vec<Vec<Vec<[f32; 2]>>> {
2388 let font = &mut self.fonts[id];
2389 let asc = font.ascent();
2390 let tol = 0.3 / px;
2391 let mut pen = 0.0f32;
2392 let mut glyphs = Vec::new();
2393 for ch in text.chars() {
2394 let go = font.glyph_outline(ch, tol);
2395 let mut contours = Vec::with_capacity(go.polylines.len());
2396 for pl in &go.polylines {
2397 let mapped: Vec<[f32; 2]> = pl
2398 .iter()
2399 .map(|p| [x + (pen + p[0]) * px, y + (asc - p[1]) * px])
2400 .collect();
2401 contours.push(mapped);
2402 }
2403 glyphs.push(contours);
2404 pen += go.advance;
2405 }
2406 glyphs
2407 }
2408
2409 pub fn register_program(&mut self, program: &Program) -> Result<(), String> {
2414 for item in &program.items {
2415 self.register_item("", item)?;
2416 }
2417 let mut env = new_env();
2418 let non_do: Vec<_> = self
2419 .globals
2420 .iter()
2421 .filter(|(_, e)| !matches!(e, Expr::Do(_)))
2422 .map(|(k, e)| (k.clone(), e.clone()))
2423 .collect();
2424 let mut pending: Vec<(String, Expr)> = Vec::new();
2425 for (k, expr) in &non_do {
2426 let mut tmp = new_env();
2427 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2428 env.insert(k.clone(), v);
2429 } else {
2430 pending.push((k.clone(), expr.clone()));
2431 }
2432 }
2433 for (k, expr) in &pending {
2434 let mut tmp = env.clone();
2435 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2436 env.insert(k.clone(), v);
2437 }
2438 }
2439 self.global_seed = env;
2440 Ok(())
2441 }
2442
2443 pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
2444 self.register_program(program)?;
2445 let entry = self
2446 .find_entry()
2447 .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
2448 let mut env = self.global_seed.clone();
2449 self.framed("start", |me| me.eval_expr(&entry, &mut env))
2450 .map(|_| ())
2451 .map_err(|e| match e {
2452 EvalErr::Runtime(s) => s,
2453 EvalErr::Return(_) => "unexpected top-level return".to_string(),
2454 EvalErr::Break => "unexpected break at top level".to_string(),
2455 })
2456 }
2457
2458 fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
2459 match item {
2460 Item::Bind(name, expr) => {
2461 let key = if ns.is_empty() {
2462 name.clone()
2463 } else {
2464 format!("{ns}::{name}")
2465 };
2466 self.globals.insert(key, expr.clone());
2467 },
2468 Item::Fn(def) => {
2469 let key = if ns.is_empty() {
2470 def.name.clone()
2471 } else {
2472 format!("{ns}::{}", def.name)
2473 };
2474 self.functions.insert(key, Rc::new(def.clone()));
2475 },
2476 Item::Mod(name, body) => {
2477 let child_ns = if ns.is_empty() {
2478 name.clone()
2479 } else {
2480 format!("{ns}::{name}")
2481 };
2482 for child in body {
2483 self.register_item(&child_ns, child)?;
2484 }
2485 },
2486 Item::TypeAlias(_, _) => {},
2487 Item::Struct(name, fields) => {
2488 self.structs.insert(name.clone(), fields.clone());
2489 if !ns.is_empty() {
2490 self.structs.insert(format!("{ns}::{name}"), fields.clone());
2491 }
2492 },
2493 Item::Enum(name, variants) => {
2494 for v in variants {
2495 self.enum_variants
2496 .insert(v.name.clone(), (name.clone(), v.arity));
2497 self.enum_variants
2498 .insert(format!("{name}::{}", v.name), (name.clone(), v.arity));
2499 if !ns.is_empty() {
2500 self.enum_variants
2501 .insert(format!("{ns}::{name}::{}", v.name), (name.clone(), v.arity));
2502 }
2503 }
2504 },
2505 Item::Use { path, alias } => {
2506 self.load_module(path, alias.as_deref(), ns)?;
2507 },
2508 }
2509 Ok(())
2510 }
2511
2512 fn load_module(
2517 &mut self,
2518 path: &str,
2519 alias: Option<&str>,
2520 parent_ns: &str,
2521 ) -> Result<(), String> {
2522 #[cfg(target_arch = "wasm32")]
2524 let (source, sub_dir) = {
2525 if self.loaded_files.contains(path) {
2527 return Ok(());
2528 }
2529 self.loaded_files.insert(path.to_string());
2530
2531 let src = crate::runtime::get_wasm_module(path)
2532 .or_else(|| crate::runtime::get_wasm_module(&format!("{}.ling", path)))
2533 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2534 (src, None::<std::path::PathBuf>)
2535 };
2536
2537 #[cfg(not(target_arch = "wasm32"))]
2539 let (source, sub_dir) = {
2540 let base_dir = self
2541 .source_dir
2542 .clone()
2543 .unwrap_or_else(|| std::path::PathBuf::from("."));
2544 let raw = std::path::Path::new(path);
2545 let candidates: Vec<std::path::PathBuf> = vec![
2546 base_dir.join(format!("{}.ling", path)),
2547 base_dir.join(format!("{}.灵", path)),
2548 base_dir.join(format!("{}.령", path)),
2549 base_dir.join(format!("{}.霊", path)),
2550 base_dir.join(format!("{}.ลิง", path)),
2551 base_dir.join(raw),
2552 std::path::PathBuf::from(format!("{}.ling", path)),
2553 std::path::PathBuf::from(path),
2554 ];
2555
2556 let resolved = candidates
2557 .into_iter()
2558 .find(|p| p.exists())
2559 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2560
2561 let canonical = resolved
2562 .canonicalize()
2563 .unwrap_or_else(|_| resolved.clone())
2564 .to_string_lossy()
2565 .to_string();
2566
2567 if self.loaded_files.contains(&canonical) {
2569 return Ok(());
2570 }
2571 self.loaded_files.insert(canonical.clone());
2572
2573 let src = std::fs::read_to_string(&resolved)
2574 .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
2575 let dir = resolved.parent().map(|p| p.to_path_buf());
2576 (src, dir)
2577 };
2578
2579 let program = crate::parser::parse(&source)
2580 .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
2581
2582 let target_ns = match (parent_ns.is_empty(), alias) {
2584 (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
2585 (_, Some(a)) => a.to_string(),
2586 (false, None) => parent_ns.to_string(),
2587 (true, None) => String::new(),
2588 };
2589
2590 let prev_dir = self.source_dir.clone();
2592 self.source_dir = sub_dir;
2593
2594 for item in &program.items {
2595 self.register_item(&target_ns, item)?;
2596 }
2597
2598 self.source_dir = prev_dir;
2599 Ok(())
2600 }
2601
2602 fn find_entry(&self) -> Option<Expr> {
2603 for key in crate::entry::ENTRY_NAMES {
2605 if let Some(e) = self.globals.get(*key) {
2606 return Some(e.clone());
2607 }
2608 }
2609 self.globals
2610 .values()
2611 .find(|e| matches!(e, Expr::Do(_)))
2612 .cloned()
2613 }
2614
2615 fn eval_expr(&mut self, expr: &Expr, env: &mut Env) -> EvalResult {
2618 match expr {
2619 Expr::Str(s) => Ok(Value::Str(s.clone())),
2620 Expr::Number(n) => Ok(Value::Number(*n)),
2621 Expr::Bool(b) => Ok(Value::Bool(*b)),
2622 Expr::Unit => Ok(Value::Unit),
2623 Expr::Array(elems) => {
2624 let vs: Vec<_> = elems
2625 .iter()
2626 .map(|e| self.eval_expr(e, env))
2627 .collect::<Result<_, _>>()?;
2628 Ok(Value::List(Rc::new(vs)))
2629 },
2630
2631 Expr::Ident(name) => self.lookup(name, env),
2632
2633 Expr::Path(segs) => {
2634 if segs.len() == 1 {
2635 return self.lookup(&segs[0], env);
2636 }
2637 Ok(Value::Str(segs.join("::")))
2638 },
2639
2640 Expr::Ref(inner) => self.eval_expr(inner, env),
2641 Expr::Await(inner) => self.eval_expr(inner, env),
2642
2643 Expr::Do(stmts) => {
2644 let mut local = env.clone();
2645 Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
2646 },
2647
2648 Expr::BinOp(op, lhs, rhs) => {
2649 let l = self.eval_expr(lhs, env)?;
2650 let r = self.eval_expr(rhs, env)?;
2651 self.apply_binop(op, l, r)
2652 },
2653
2654 Expr::If { cond, then, elseifs, else_body } => {
2655 let cond_val = self.eval_expr(cond, env)?;
2656 if self.is_truthy(&cond_val) {
2657 return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
2658 }
2659 for (ei_cond, ei_body) in elseifs {
2660 let ei_cond_val = self.eval_expr(ei_cond, env)?;
2661 if self.is_truthy(&ei_cond_val) {
2662 return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
2663 }
2664 }
2665 if let Some(eb) = else_body {
2666 return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
2667 }
2668 Ok(Value::Unit)
2669 },
2670
2671 Expr::While { cond, body } => {
2672 loop {
2676 let cv = self.eval_expr(cond, env)?;
2677 if !self.is_truthy(&cv) {
2678 break;
2679 }
2680 match self.exec_block(body, env) {
2681 Ok(_) => {},
2682 Err(EvalErr::Break) => break,
2683 Err(e) => return Err(e),
2684 }
2685 }
2686 Ok(Value::Unit)
2687 },
2688
2689 Expr::For { var, iter, body } => {
2690 let iter_val = self.eval_expr(iter, env)?;
2691 let items = self.value_to_iter(iter_val)?;
2692 for item in items {
2693 let mut local = env.clone();
2694 local.insert(var.clone(), item);
2695 match self.exec_block(body, &mut local) {
2696 Ok(_) => {},
2697 Err(EvalErr::Break) => break,
2698 Err(e) => return Err(e),
2699 }
2700 }
2701 Ok(Value::Unit)
2702 },
2703
2704 Expr::Match(subject, arms) => {
2705 let subj = self.eval_expr(subject, env)?;
2706 for arm in arms {
2707 if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
2708 let mut local = env.clone();
2709 local.extend(bindings);
2710 return self.eval_expr(&arm.body, &mut local);
2711 }
2712 }
2713 Ok(Value::Unit)
2714 },
2715
2716 Expr::Range(lo, hi) => {
2717 let lo_v = self.eval_expr(lo, env)?;
2718 let hi_v = self.eval_expr(hi, env)?;
2719 let lo_n = self.to_number(&lo_v)? as i64;
2720 let hi_n = self.to_number(&hi_v)? as i64;
2721 Ok(Value::List(Rc::new(
2722 (lo_n..hi_n).map(|i| Value::Number(i as f64)).collect(),
2723 )))
2724 },
2725
2726 Expr::Index(base, idx) => {
2727 let b = self.eval_expr(base, env)?;
2728 let i = self.eval_expr(idx, env)?;
2729 let n = self.to_number(&i)? as usize;
2730 match b {
2731 Value::List(v) => v
2732 .get(n)
2733 .cloned()
2734 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2735 Value::Str(s) => s
2736 .chars()
2737 .nth(n)
2738 .map(|c| Value::Str(c.to_string()))
2739 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2740 other => Err(EvalErr::from(format!("cannot index {:?}", other))),
2741 }
2742 },
2743
2744 Expr::Call(callee, args) => {
2745 let arg_vals: Vec<Value> = args
2746 .iter()
2747 .map(|a| self.eval_expr(a, env))
2748 .collect::<Result<_, _>>()?;
2749 match callee.as_ref() {
2750 Expr::Ident(name) => self.call_named(name, arg_vals, env),
2751 Expr::Path(segs) => self.call_named(&segs.join("::"), arg_vals, env),
2752 _ => {
2753 let v = self.eval_expr(callee, env)?;
2754 self.call_value(v, arg_vals)
2755 },
2756 }
2757 },
2758
2759 Expr::MethodCall { receiver, method, args } => {
2760 let recv = self.eval_expr(receiver, env)?;
2761 let arg_vals: Vec<Value> = args
2762 .iter()
2763 .map(|a| self.eval_expr(a, env))
2764 .collect::<Result<_, _>>()?;
2765 self.call_method(recv, method, arg_vals)
2766 },
2767
2768 Expr::Closure(params, body) => Ok(Value::Fn(
2769 params.clone(),
2770 vec![Stmt::Expr(*body.clone())],
2771 env.clone(),
2772 )),
2773
2774 Expr::Asm(_) => Ok(Value::Unit),
2775 }
2776 }
2777
2778 fn exec_block(&mut self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
2781 let mut last: Option<Value> = None;
2782 for stmt in stmts {
2783 match stmt {
2784 Stmt::Bind(name, expr) => {
2785 match self.try_inplace_list_update(name, expr, env)? {
2786 Some(v) => env.insert(name.clone(), v),
2787 None => {
2788 let v = self.eval_expr(expr, env)?;
2789 env.insert(name.clone(), v)
2790 },
2791 };
2792 last = None;
2793 },
2794 Stmt::Return(expr) => {
2795 let v = self.eval_expr(expr, env)?;
2796 return Err(EvalErr::Return(v));
2797 },
2798 Stmt::Expr(expr) => {
2799 last = Some(self.eval_expr(expr, env)?);
2800 },
2801 }
2802 }
2803 Ok(last)
2804 }
2805
2806 fn try_inplace_list_update(
2813 &mut self,
2814 name: &str,
2815 expr: &Expr,
2816 env: &mut Env,
2817 ) -> Result<Option<Value>, EvalErr> {
2818 let Expr::Call(callee, args) = expr else { return Ok(None) };
2819 let Expr::Ident(fname) = callee.as_ref() else { return Ok(None) };
2820 let is_push = matches!(
2821 fname.as_str(),
2822 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가"
2823 );
2824 let is_set = matches!(
2825 fname.as_str(),
2826 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정"
2827 );
2828 if !is_push && !is_set {
2829 return Ok(None);
2830 }
2831 match args.first() {
2834 Some(Expr::Ident(a0)) if a0 == name => {},
2835 _ => return Ok(None),
2836 }
2837 if self.functions.contains_key(fname.as_str()) {
2838 return Ok(None);
2839 }
2840 if is_push {
2841 if args.len() != 2 {
2842 return Ok(None);
2843 }
2844 let val = self.eval_expr(&args[1], env)?;
2845 match env.remove(name) {
2846 Some(Value::List(mut v)) => {
2847 Rc::make_mut(&mut v).push(val);
2848 Ok(Some(Value::List(v)))
2849 },
2850 other => {
2851 if let Some(o) = other {
2852 env.insert(name.to_string(), o);
2853 }
2854 Ok(None)
2855 },
2856 }
2857 } else {
2858 if args.len() != 3 {
2859 return Ok(None);
2860 }
2861 let idx_v = self.eval_expr(&args[1], env)?;
2862 let idx = self.to_number(&idx_v).unwrap_or(0.0) as usize;
2863 let val = self.eval_expr(&args[2], env)?;
2864 match env.remove(name) {
2865 Some(Value::List(mut v)) => {
2866 if idx < v.len() {
2867 Rc::make_mut(&mut v)[idx] = val;
2868 }
2869 Ok(Some(Value::List(v)))
2870 },
2871 other => {
2872 if let Some(o) = other {
2873 env.insert(name.to_string(), o);
2874 }
2875 Ok(None)
2876 },
2877 }
2878 }
2879 }
2880
2881 fn lookup(&self, name: &str, env: &Env) -> EvalResult {
2884 if let Some(v) = env.get(name) {
2885 return Ok(v.clone());
2886 }
2887 if let Some(v) = self.global_seed.get(name) {
2890 return Ok(v.clone());
2891 }
2892 if self.functions.contains_key(name) {
2893 let def = &self.functions[name];
2894 return Ok(Value::Fn(def.params.clone(), def.body.clone(), new_env()));
2895 }
2896 if let Some((enum_name, 0)) = self.enum_variants.get(name).cloned() {
2898 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
2899 return Ok(Value::Variant { enum_name, variant, payload: Vec::new() });
2900 }
2901 match name {
2903 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی" | "пи" => {
2904 return Ok(Value::Number(std::f64::consts::PI))
2905 },
2906 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
2907 return Ok(Value::Number(std::f64::consts::TAU))
2908 },
2909 _ => {},
2910 }
2911 Err(EvalErr::from(format!("undefined: '{name}'")))
2912 }
2913
2914 pub(crate) fn call_named(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2923 if !ling_profile_enabled() {
2924 return self.call_named_inner(name, args, env);
2925 }
2926 let t0 = crate::runtime::now_secs();
2927 let r = self.call_named_inner(name, args, env);
2928 ling_profile_record(
2929 name,
2930 ((crate::runtime::now_secs() - t0) * 1_000_000_000.0) as u128,
2931 );
2932 r
2933 }
2934
2935 fn call_named_inner(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2936 if let Some(def) = self.functions.get(name).cloned() {
2939 let mut call_env =
2940 FxHashMap::with_capacity_and_hasher(def.params.len(), Default::default());
2941 let _ = env; for (param, arg) in def.params.iter().zip(args) {
2943 call_env.insert(param.clone(), arg);
2944 }
2945 return match self.framed(name, |me| me.exec_block(&def.body, &mut call_env)) {
2946 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
2947 Err(EvalErr::Return(v)) => Ok(v),
2948 Err(e) => Err(e),
2949 };
2950 }
2951
2952 #[cfg(target_arch = "wasm32")]
2953 if let Some(v) = self.wasm_music_builtin(name, &args)? {
2954 return Ok(v);
2955 }
2956
2957 match name {
2958 "__ling_global" => {
2961 if let Some(Value::Str(g)) = args.first() {
2962 if let Some(v) = self.global_seed.get(g.as_str()) {
2963 return Ok(v.clone());
2964 }
2965 }
2966 return Ok(Value::Unit);
2967 },
2968 "print" | "println" | "印" | "打印" | "印刷" | "พิมพ์" | "출력" | "вывести"
2970 | "imprimir" | "afficher" | "چاپ" | "اطبع" | "הדפס" | "چھاپو" | "drucken" | "печать" => {
2971 let s = args
2972 .iter()
2973 .map(|v| v.to_string())
2974 .collect::<Vec<_>>()
2975 .join("");
2976 println!("{s}");
2977 return Ok(Value::Unit);
2978 },
2979 "print_color" | "พิมพ์สี" | "چاپ_رنگی" | "اطبع_بلون" | "הדפס_בצבע" | "رنگین_چھاپو" => {
2982 #[cfg(windows)]
2983 {
2984 use std::sync::Once;
2985 static VT: Once = Once::new();
2986 VT.call_once(|| {
2987 extern "system" {
2988 fn GetStdHandle(n: u32) -> *mut std::ffi::c_void;
2989 fn GetConsoleMode(h: *mut std::ffi::c_void, m: *mut u32) -> i32;
2990 fn SetConsoleMode(h: *mut std::ffi::c_void, m: u32) -> i32;
2991 }
2992 unsafe {
2993 let h = GetStdHandle(0xFFFF_FFF5u32); let mut mode = 0u32;
2995 if GetConsoleMode(h, &mut mode) != 0 {
2996 SetConsoleMode(h, mode | 0x0004); }
2998 }
2999 });
3000 }
3001 let col = self.arg_num(&args, 0, 7.0)? as i64;
3002 let s = args
3003 .iter()
3004 .skip(1)
3005 .map(|v| v.to_string())
3006 .collect::<Vec<_>>()
3007 .join("");
3008 let code = 90 + col.clamp(0, 7);
3009 println!("\x1b[1;{code}m{s}\x1b[0m");
3010 return Ok(Value::Unit);
3011 },
3012 "format"
3014 | "格式"
3015 | "フォーマット"
3016 | "서식"
3017 | "รูปแบบ"
3018 | "форматировать"
3019 | "formatear"
3020 | "formater" | "قالببندی" | "نسّق" | "פרמט" | "فارمیٹ" | "formatieren" => {
3021 return Ok(Value::Str(self.builtin_format(&args)?));
3022 },
3023 "格式::拼接" | "format::join" => match args.first() {
3025 Some(Value::List(items)) => {
3026 return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
3027 },
3028 _ => return Ok(Value::Str(self.builtin_format(&args)?)),
3029 },
3030 "ok" | "好" | "良し" | "좋아" | "โอเค" | "تایید" | "تمام" | "בסדר" | "ٹھیک" | "bon" | "gut" | "хорошо" => {
3032 let val = args.into_iter().next().unwrap_or(Value::Unit);
3033 return Ok(Value::Ok(Box::new(val)));
3034 },
3035 "bad" | "坏" | "err" | "悪い" | "나쁨" | "ผิด" | "بد" | "سيء" | "רע" | "برا" | "mauvais" | "schlecht" | "плохо" => {
3036 let val = args.into_iter().next().unwrap_or(Value::Unit);
3037 return Ok(Value::Err(Box::new(val)));
3038 },
3039 "向量::从" | "Vec::from" => {
3041 if let Some(Value::List(v)) = args.first() {
3042 return Ok(Value::List(v.clone()));
3043 }
3044 return Ok(Value::List(Rc::new(args)));
3045 },
3046 "向量::有容量" | "Vec::with_capacity" => {
3047 return Ok(Value::List(Rc::new(Vec::new())))
3048 },
3049 "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
3051 "计时::现在" | "Timer::now" => return Ok(Value::Number(1000.0)),
3052 "sleep" | "หยุด" | "นอน" | "sleep_ms" | "睡眠" | "眠る" | "スリープ" | "잠자기"
3054 | "잠" | "流水::睡眠" | "Flow::sleep" | "خواب" | "نم" | "שינה" | "سو_جاؤ" | "dormir" | "schlafen" | "спать" => {
3055 if let Some(ms_val) = args.first() {
3056 if let Ok(ms) = self.to_number(ms_val) {
3057 #[cfg(target_arch = "wasm32")]
3058 wasm_sleep_ms(ms.max(0.0) as i32);
3059 #[cfg(not(target_arch = "wasm32"))]
3060 std::thread::sleep(std::time::Duration::from_millis(ms as u64));
3061 }
3062 }
3063 return Ok(Value::Unit);
3064 },
3065 "流水::并行" | "Flow::parallel" => {
3067 if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
3068 let _ = params;
3069 match self.exec_block(&body, &mut cap) {
3070 Ok(Some(v)) => return Ok(v),
3071 Ok(None) => return Ok(Value::Unit),
3072 Err(EvalErr::Return(v)) => return Ok(v),
3073 Err(e) => return Err(e),
3074 }
3075 }
3076 return Ok(Value::Unit);
3077 },
3078
3079 "sin" | "ไซน์" | "正弦" | "サイン" | "사인" | "سینوس" | "جا" | "סינוס" | "سائن" | "sinus" | "синус" => {
3088 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
3089 },
3090 "cos" | "โคไซน์" | "余弦" | "コサイン" | "코사인" | "کسینوس" | "جتا" | "קוסינוס" | "کوسائن" | "cosinus" | "kosinus" | "косинус" => {
3091 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
3092 },
3093
3094 "tanh" | "tanhf" | "双曲正切" | "双曲線正接" | "쌍곡탄젠트" => {
3097 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
3098 },
3099
3100 "tan" | "แทนเจนต์" | "正切" | "タンジェント" | "탄젠트" | "تانژانت" | "ظا" | "טנגנס" | "ٹینجنٹ" | "tangente" | "tangens" | "тангенс" => {
3101 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
3102 },
3103 "asin" | "arcsin" | "反正弦" | "アークサイン" | "아크사인" | "อาร์กไซน์" | "آرکسینوس" | "قوس_جا" | "ארקסינוס" | "آرک_سائن" | "arcsinus" | "arkussinus" | "арксинус" =>
3104 {
3105 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
3106 },
3107 "acos" | "arccos" | "反余弦" | "アークコサイン" | "아크코사인" | "อาร์กโคไซน์" | "آرککسینوس" | "قوس_جتا" | "ארקוקוסינוס" | "آرک_کوسائن" | "arccosinus" | "arkuskosinus" | "арккосинус" =>
3108 {
3109 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
3110 },
3111 "atan" | "arctan" | "反正切" | "アークタンジェント" | "아크탄젠트" | "อาร์กแทนเจนต์" | "آرکتانژانت" | "قوس_ظا" | "ארקטנגנס" | "آرک_ٹینجنٹ" | "arctangente" | "arkustangens" | "арктангенс" =>
3112 {
3113 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
3114 },
3115 "atan2" | "arctan2" | "反正切2" | "アークタンジェント2" | "아크탄젠트2" =>
3116 {
3117 let y = self.arg_num(&args, 0, 0.0)?;
3118 let x = self.arg_num(&args, 1, 1.0)?;
3119 return Ok(Value::Number(y.atan2(x)));
3120 },
3121
3122 "sqrt" | "รากที่สอง" | "平方根" | "根" | "제곱근" | "جذر" | "שורש" | "racine_carrée" | "quadratwurzel" | "корень" => {
3124 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
3125 },
3126 "cbrt" | "立方根" | "세제곱근" | "รากที่สาม" | "ریشه_سوم" | "جذر_تكعيبي" | "שורש_שלישי" | "مکعب_جذر" | "racine_cubique" | "kubikwurzel" | "кубический_корень" => {
3127 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
3128 },
3129 "pow" | "ยกกำลัง" | "幂" | "べき乗" | "거듭제곱" | "توان" | "أس" | "חזקה" | "قوت" | "puissance" | "potenz" | "степень" => {
3130 let base = self.arg_num(&args, 0, 0.0)?;
3131 let exp = self.arg_num(&args, 1, 1.0)?;
3132 return Ok(Value::Number(base.powf(exp)));
3133 },
3134 "exp" | "指数" | "指数関数" | "지수" => {
3135 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
3136 },
3137 "hypot" | "斜边" | "斜辺" | "빗변" => {
3138 let x = self.arg_num(&args, 0, 0.0)?;
3139 let y = self.arg_num(&args, 1, 0.0)?;
3140 return Ok(Value::Number(x.hypot(y)));
3141 },
3142
3143 "ln" | "log" | "ลอการิทึม" | "对数" | "対数" | "로그" | "لگاریتم_طبیعی" | "لوغاريتم_طبيعي" | "לוגריתם_טבעי" | "فطری_لوگ" => {
3145 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
3146 },
3147 "log2" | "对数2" | "対数2" | "로그2" => {
3148 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
3149 },
3150 "log10" | "对数10" | "対数10" | "로그10" => {
3151 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
3152 },
3153
3154 "abs" | "ค่าสัมบูรณ์" | "绝对值" | "绝对" | "絶対値" | "절댓값" | "절대값" | "قدرمطلق" | "مطلق" | "ערך_מוחלט" | "مطلق_قدر" | "valeur_absolue" | "betrag" | "модуль_числа" =>
3156 {
3157 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
3158 },
3159 "floor" | "ปัดลง" | "向下取整" | "下整" | "床関数" | "내림" | "کف" | "أرضية" | "רצפה" | "فرش" | "plancher" | "abrunden" | "вниз" => {
3160 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
3161 },
3162 "ceil" | "ปัดขึ้น" | "向上取整" | "上整" | "天井関数" | "올림" | "سقف" | "תקרה" | "چھت" | "plafond" | "aufrunden" | "вверх" =>
3163 {
3164 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
3165 },
3166 "round" | "ปัดเศษ" | "四舍五入" | "四舍" | "四捨五入" | "반올림" | "گرد_کردن" | "تقريب" | "עיגול" | "گول" | "arrondir" | "runden" | "округлить" =>
3167 {
3168 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
3169 },
3170 "trunc"
3171 | "int"
3172 | "ตัดทศนิยม"
3173 | "取整"
3174 | "整数化"
3175 | "整数"
3176 | "截整"
3177 | "정수화"
3178 | "정수"
3179 | "切り捨て"
3180 | "버림" | "برش" | "اقتطاع" | "קיטום" | "کٹائی" | "tronquer" | "abschneiden" | "усечь" => {
3181 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
3182 },
3183 "fract" | "小数部分" | "小数部" | "소수부" => {
3184 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
3185 },
3186
3187 "min" | "ต่ำสุด" | "最小" | "최솟값" | "کمینه" | "أصغر" | "מינימום" | "کم_ترین" | "minimum" | "минимум" => {
3189 let a = self.arg_num(&args, 0, 0.0)?;
3190 let b = self.arg_num(&args, 1, 0.0)?;
3191 return Ok(Value::Number(a.min(b)));
3192 },
3193 "max" | "สูงสุด" | "最大" | "최댓값" | "بیشینه" | "أكبر" | "מקסימום" | "زیادہ_ترین" | "maximum" | "максимум" => {
3194 let a = self.arg_num(&args, 0, 0.0)?;
3195 let b = self.arg_num(&args, 1, 0.0)?;
3196 return Ok(Value::Number(a.max(b)));
3197 },
3198 "clamp" | "จำกัด" | "截取" | "範囲制限" | "범위제한" | "محدود" | "قيّد" | "הגבל" | "limiter" | "begrenzen" | "ограничить" => {
3199 let x = self.arg_num(&args, 0, 0.0)?;
3200 let lo = self.arg_num(&args, 1, 0.0)?;
3201 let hi = self.arg_num(&args, 2, 1.0)?;
3202 return Ok(Value::Number(x.clamp(lo, hi)));
3203 },
3204
3205 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی" | "пи" => {
3207 return Ok(Value::Number(std::f64::consts::PI))
3208 },
3209 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
3210 return Ok(Value::Number(std::f64::consts::TAU))
3211 },
3212
3213 "vnoise" | "noise2" | "นอยส์2ดี" | "柏林噪声2D" | "バリューノイズ2D" | "값노이즈2D" | "نویز_برداری" | "ضجيج_متجه" | "רעש_וקטורי" | "ویکٹر_نوائز" | "bruit_v" | "v_rauschen" | "шум_v" =>
3219 {
3220 let x = self.arg_num(&args, 0, 0.0)? as f32;
3221 let y = self.arg_num(&args, 1, 0.0)? as f32;
3222 let seed = self.arg_num(&args, 2, 0.0)? as u32;
3223 return Ok(Value::Number(tex_vnoise(x, y, seed) as f64));
3224 },
3225
3226 "fbm" | "นอยส์ออร์แกนิก" | "分形噪声" | "フラクタルノイズ" | "프랙탈노이즈" | "نویز_فراکتالی" | "ضجيج_عضوي" | "רעש_פרקטלי" | "فریکٹل_نوائز" =>
3227 {
3228 let x = self.arg_num(&args, 0, 0.0)? as f32;
3229 let y = self.arg_num(&args, 1, 0.0)? as f32;
3230 let octaves = self.arg_num(&args, 2, 4.0)? as u32;
3231 let seed = self.arg_num(&args, 3, 0.0)? as u32;
3232 return Ok(Value::Number(tex_fbm(x, y, octaves, seed) as f64));
3233 },
3234
3235 "perlin"
3236 | "perlin3"
3237 | "เพอร์ลิน3ดี"
3238 | "柏林噪声3D"
3239 | "パーリンノイズ3D"
3240 | "펄린노이즈3D" | "نویز_پرلین" | "بيرلين" | "רעש_פרלין" | "پرلن_نوائز" | "перлин" => {
3241 let x = self.arg_num(&args, 0, 0.0)? as f32;
3242 let y = self.arg_num(&args, 1, 0.0)? as f32;
3243 let z = self.arg_num(&args, 2, 0.0)? as f32;
3244 return Ok(Value::Number(perlin3(x, y, z) as f64));
3245 },
3246
3247 "lerp" | "ค่าระหว่าง" | "线性插值" | "線形補間" | "선형보간" | "میانیابی" | "استيفاء" | "אינטרפולציה" | "درمیانی_قدر" | "interpoler" | "interpolieren" | "интерполировать" =>
3249 {
3250 let a = self.arg_num(&args, 0, 0.0)?;
3251 let b = self.arg_num(&args, 1, 1.0)?;
3252 let t = self.arg_num(&args, 2, 0.0)?;
3253 return Ok(Value::Number(a + (b - a) * t));
3254 },
3255
3256 "smoothstep" | "เปลี่ยนแบบนุ่ม" | "平滑步进" | "スムーズステップ" | "스무스스텝" | "گام_نرم" | "تدرج_ناعم" | "מדרגה_חלקה" | "ہموار_قدم" | "lissage" | "glättung" | "сглаживание" =>
3257 {
3258 let lo = self.arg_num(&args, 0, 0.0)?;
3259 let hi = self.arg_num(&args, 1, 1.0)?;
3260 let x = self.arg_num(&args, 2, 0.5)?;
3261 let t = ((x - lo) / (hi - lo)).clamp(0.0, 1.0);
3262 return Ok(Value::Number(t * t * (3.0 - 2.0 * t)));
3263 },
3264
3265 "rand" | "สุ่ม" | "随机" | "乱数" | "난수" | "تصادفی" | "عشوائي" | "אקראי" | "بے_ترتیب" | "aléatoire" | "zufall" | "случайное" => {
3266 let val = fast_rand_f64(&mut self.rand_state);
3267 return Ok(Value::Number(val));
3268 },
3269
3270 "sign" | "เครื่องหมาย" | "符号" | "符号関数" | "부호" | "علامت" | "إشارة" | "סימן" | "نشان" | "signe" | "vorzeichen" | "знак" => {
3271 let x = self.arg_num(&args, 0, 0.0)?;
3272 return Ok(Value::Number(x.signum()));
3273 },
3274
3275 "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" =>
3276 {
3277 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;
3281 let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
3282 let m = v - c;
3283 let (r1, g1, b1) = if h < 60.0 {
3284 (c, x, 0.0)
3285 } else if h < 120.0 {
3286 (x, c, 0.0)
3287 } else if h < 180.0 {
3288 (0.0, c, x)
3289 } else if h < 240.0 {
3290 (0.0, x, c)
3291 } else if h < 300.0 {
3292 (x, 0.0, c)
3293 } else {
3294 (c, 0.0, x)
3295 };
3296 let r = ((r1 + m) * 255.0).round();
3297 let g = ((g1 + m) * 255.0).round();
3298 let b = ((b1 + m) * 255.0).round();
3299 return Ok(Value::List(Rc::new(vec![
3300 Value::Number(r),
3301 Value::Number(g),
3302 Value::Number(b),
3303 ])));
3304 },
3305
3306 "lerp_color" | "ไล่สี" | "颜色插值" | "色補間" | "색보간" | "میانیابی_رنگ" | "استيفاء_اللون" | "אינטרפולציית_צבע" | "رنگ_درمیانی_قدر" | "interpoler_couleur" | "farbe_interpolieren" | "интерполировать_цвет" => {
3307 let r1 = self.arg_num(&args, 0, 0.0)?;
3308 let g1 = self.arg_num(&args, 1, 0.0)?;
3309 let b1 = self.arg_num(&args, 2, 0.0)?;
3310 let r2 = self.arg_num(&args, 3, 255.0)?;
3311 let g2 = self.arg_num(&args, 4, 255.0)?;
3312 let b2 = self.arg_num(&args, 5, 255.0)?;
3313 let t = self.arg_num(&args, 6, 0.0)?;
3314 let r = r1 + (r2 - r1) * t;
3315 let g = g1 + (g2 - g1) * t;
3316 let b = b1 + (b2 - b1) * t;
3317 let c = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
3318 self.gfx.borrow_mut().color = c;
3319 return Ok(Value::Unit);
3320 },
3321
3322 "time_now" | "เวลาปัจจุบัน" | "当前时间" | "経過時間" | "현재시간" | "زمان_اکنون" | "الوقت_الآن" | "הזמן_עכשיו" | "ابھی_کا_وقت" | "temps_actuel" | "aktuelle_zeit" | "текущее_время" =>
3324 {
3325 return Ok(Value::Number(
3326 crate::runtime::now_secs() - self.start_time_secs,
3327 ));
3328 },
3329
3330 "epoch_now" | "เวลาโลก" | "datetime" | "现在时刻" | "現在時刻" | "현재시각" | "مهر_زمانی_اکنون" | "طابع_الوقت_الآن" | "חותמת_זמן_עכשיו" | "ایپاک_وقت" =>
3334 {
3335 return Ok(Value::Number(crate::runtime::now_secs()));
3336 },
3337
3338 "frame_count" | "เฟรม" | "帧数" | "フレーム数" | "프레임수" | "شمار_فریم" | "عدد_الإطارات" | "ספירת_פריימים" | "فریم_شمار" | "nombre_images" | "bildanzahl" | "число_кадров" => {
3339 return Ok(Value::Number(self.frame_num as f64));
3340 },
3341
3342 "mic_open" | "เปิดไมค์" | "开麦克风" | "マイク開く" | "마이크열기" | "باز_کردن_میکروفون" | "افتح_الميكروفون" | "פתח_מיקרופון" | "مائیکروفون_کھولو" | "ouvrir_micro" | "mikrofon_öffnen" | "открыть_микрофон" =>
3344 {
3345 #[cfg(not(target_arch = "wasm32"))]
3346 {
3347 match ling_mic::MicInput::open(Default::default()) {
3348 Ok(mic) => {
3349 let _ = mic.start(|_samples: &[f32]| {}); self.mic = Some(mic);
3351 return Ok(Value::Number(1.0)); },
3353 Err(_e) => {
3356 self.mic = None;
3357 return Ok(Value::Number(0.0));
3358 },
3359 }
3360 }
3361 #[cfg(target_arch = "wasm32")]
3362 return Ok(Value::Unit);
3363 },
3364
3365 "mic_rms" | "เสียงRMS" | "麦克风音量" | "マイクRMS" | "마이크RMS" | "RMS_میکروفون" | "RMS_الميكروفون" | "RMS_מיקרופון" | "مائیکروفون_RMS" | "rms_micro" | "mikrofon_rms" | "rms_микрофона" =>
3366 {
3367 #[cfg(not(target_arch = "wasm32"))]
3368 {
3369 let rms = self
3370 .mic
3371 .as_ref()
3372 .map(|m: &ling_mic::MicInput| m.rms())
3373 .unwrap_or(0.0);
3374 return Ok(Value::Number(rms as f64));
3375 }
3376 #[cfg(target_arch = "wasm32")]
3377 return Ok(Value::Number(0.0));
3378 },
3379
3380 "mic_peak" | "เสียงพีค" | "麦克风峰值" | "マイクピーク" | "마이크피크" | "اوج_میکروفون" | "ذروة_الميكروفون" | "שיא_מיקרופון" | "مائیکروفون_چوٹی" | "crête_micro" | "mikrofon_spitze" | "пик_микрофона" =>
3381 {
3382 #[cfg(not(target_arch = "wasm32"))]
3383 {
3384 let peak = self
3385 .mic
3386 .as_ref()
3387 .map(|m: &ling_mic::MicInput| m.peak())
3388 .unwrap_or(0.0);
3389 return Ok(Value::Number(peak as f64));
3390 }
3391 #[cfg(target_arch = "wasm32")]
3392 return Ok(Value::Number(0.0));
3393 },
3394
3395 "mic_fft" | "วิเคราะห์เสียงสด" | "实时频谱" | "リアルタイムFFT" | "실시간FFT" | "FFT_میکروفون" | "FFT_الميكروفون" | "FFT_מיקרופון" | "مائیکروفون_FFT" | "fft_micro" | "mikrofon_fft" | "fft_микрофона" =>
3396 {
3397 #[cfg(not(target_arch = "wasm32"))]
3398 {
3399 let n = self.arg_num(&args, 0, 8.0)? as usize;
3400 if let Some(mic) = self.mic.as_ref() {
3401 let samples = mic.latest_samples();
3402 self.fft.borrow_mut().push_samples(&samples);
3403 }
3404 let bands = self.fft.borrow().freq_bands(n);
3405 let result: Vec<Value> =
3406 bands.iter().map(|&v| Value::Number(v as f64)).collect();
3407 return Ok(Value::List(Rc::new(result)));
3408 }
3409 #[cfg(target_arch = "wasm32")]
3410 return Ok(Value::List(Vec::new().into()));
3411 },
3412
3413 "set_blend" | "โหมดผสม" | "混合模式" | "ブレンドモード" | "블렌드모드" | "تنظیم_ترکیب" | "عيّن_المزج" | "קבע_מיזוג" | "بلینڈ_مقرر_کرو" | "définir_mélange" | "mischmodus_setzen" | "задать_смешивание" =>
3415 {
3416 let mode = self.arg_num(&args, 0, 0.0)? as u8;
3417 let mut gfx = self.gfx.borrow_mut();
3418 gfx.blend = mode;
3419 let a = gfx.alpha;
3420 gfx.depth_queue.set_state(mode, a); return Ok(Value::Unit);
3422 },
3423
3424 "set_antialias" | "ตั้งลบรอยหยัก" | "抗锯齿" | "アンチエイリアス" | "안티에일리어싱" | "تنظیم_ضدلبهدندانه" | "عيّن_مضاد_التسنن" | "קבע_החלקת_קצוות" | "اینٹی_الائیسنگ_مقرر_کرو" =>
3428 {
3429 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3430 self.gfx.borrow_mut().antialias = on;
3431 return Ok(Value::Unit);
3432 },
3433 "get_antialias"
3435 | "อ่านลบรอยหยัก"
3436 | "读取抗锯齿"
3437 | "アンチエイリアス取得"
3438 | "안티에일리어싱상태" | "خواندن_ضدلبهدندانه" | "اقرأ_مضاد_التسنن" | "קרא_החלקת_קצוות" | "اینٹی_الائیسنگ_پڑھو" => {
3439 return Ok(Value::Bool(self.gfx.borrow().antialias));
3440 },
3441
3442 "set_font_antialias" | "글꼴안티에일리어싱" => {
3446 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3447 self.gfx.borrow_mut().font_antialias = on;
3448 return Ok(Value::Unit);
3449 },
3450 "get_font_antialias" | "글꼴안티에일리어싱상태" => {
3452 return Ok(Value::Bool(self.gfx.borrow().font_antialias));
3453 },
3454
3455 "draw_circle" | "วาดวงกลม" | "画圆" | "円描画" | "원그리기" | "رسم_دایره" | "ارسم_دائرة" | "צייר_עיגול" | "دائرہ_کھینچو" | "dessiner_cercle" | "kreis_zeichnen" | "рисовать_круг" =>
3457 {
3458 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3459 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3460 let r = self.arg_num(&args, 2, 10.0)? as i32;
3461 let mut gfx = self.gfx.borrow_mut();
3462 let (w, h, color, blend) =
3463 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3464 if gfx.antialias {
3465 let (uw, uh) = (gfx.width, gfx.height);
3466 let segs = ((r.max(1) as u32) * 4).clamp(24, 512);
3467 crate::gfx::raster::draw_arc(
3468 &mut gfx.buffer,
3469 uw,
3470 uh,
3471 color,
3472 true,
3473 blend == 1,
3474 cx as f32,
3475 cy as f32,
3476 r as f32,
3477 0.0,
3478 std::f32::consts::TAU,
3479 segs,
3480 );
3481 } else {
3482 draw_circle_outline(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3483 }
3484 return Ok(Value::Unit);
3485 },
3486
3487 "draw_filled_circle"
3488 | "draw_disc"
3489 | "วาดวงกลมทึบ"
3490 | "画实心圆"
3491 | "塗りつぶし円"
3492 | "원채우기" | "رسم_دایره_توپر" | "ارسم_دائرة_ممتلئة" | "צייר_עיגול_מלא" | "بھرا_دائرہ_کھینچو" => {
3493 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3494 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3495 let r = self.arg_num(&args, 2, 10.0)? as i32;
3496 let mut gfx = self.gfx.borrow_mut();
3497 let (w, h, color, blend) =
3498 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3499 draw_circle_filled(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3500 return Ok(Value::Unit);
3501 },
3502
3503 "draw_arc" | "arc" | "วาดส่วนโค้ง" | "画弧" | "円弧描画" | "호그리기" | "رسم_کمان" | "ارسم_قوسا" | "צייר_קשת" | "آرک_کھینچو" =>
3507 {
3508 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3509 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3510 let r = self.arg_num(&args, 2, 10.0)? as f32;
3511 let a0 = self.arg_num(&args, 3, 0.0)? as f32;
3512 let a1 = self.arg_num(&args, 4, std::f64::consts::TAU)? as f32;
3513 let default_segs = ((r.abs() * (a1 - a0).abs()).ceil() as u32).clamp(8, 1024);
3514 let segs = self.arg_num(&args, 5, default_segs as f64)? as u32;
3515 let mut gfx = self.gfx.borrow_mut();
3516 let color = gfx.color;
3517 #[cfg(not(target_arch = "wasm32"))]
3518 {
3519 let (uw, uh, aa, add) = (gfx.width, gfx.height, gfx.antialias, gfx.blend == 1);
3520 crate::gfx::raster::draw_arc(
3521 &mut gfx.buffer,
3522 uw,
3523 uh,
3524 color,
3525 aa,
3526 add,
3527 cx,
3528 cy,
3529 r,
3530 a0,
3531 a1,
3532 segs,
3533 );
3534 }
3535 #[cfg(target_arch = "wasm32")]
3536 {
3537 let segs_f = segs.max(1);
3538 let step = (a1 - a0) / segs_f as f32;
3539 let mut px = cx + r * a0.cos();
3540 let mut py = cy + r * a0.sin();
3541 let mut i = 1u32;
3542 while i <= segs_f {
3543 let a = a0 + step * i as f32;
3544 let nx = cx + r * a.cos();
3545 let ny = cy + r * a.sin();
3546 gfx.depth_queue.push_line(0.0, color, px, py, nx, ny);
3547 px = nx;
3548 py = ny;
3549 i += 1;
3550 }
3551 }
3552 return Ok(Value::Unit);
3553 },
3554
3555 "set_alpha" | "ตั้งความโปร่งใส" | "设透明" | "アルファ設定" | "투명도설정" | "تنظیم_شفافیت" | "عيّن_الشفافية" | "קבע_שקיפות" | "شفافیت_مقرر_کرو" | "définir_alpha" | "alpha_setzen" | "задать_альфа" =>
3561 {
3562 let a = self.arg_num(&args, 0, 1.0)? as f32;
3563 let mut gfx = self.gfx.borrow_mut();
3564 gfx.alpha = a.clamp(0.0, 1.0);
3565 let (m, al) = (gfx.blend, gfx.alpha);
3566 gfx.depth_queue.set_state(m, al); return Ok(Value::Unit);
3568 },
3569
3570 "mesh_hue" | "หมุนสีเมช" | "فام_مش" | "صبغة_الشبكة" | "גוון_רשת" | "میش_ہیو" =>
3573 {
3574 let h = self.arg_num(&args, 0, 0.0)? as f32;
3575 let g = self.arg_num(&args, 1, 1.0)? as f32;
3576 let mut gfx = self.gfx.borrow_mut();
3577 gfx.mesh_hue = h;
3578 gfx.mesh_hue_gain = g.max(0.0);
3579 return Ok(Value::Unit);
3580 },
3581
3582 "set_frame_blur" | "frame_blur" | "เบลอเฟรม" | "تنظیم_تاری_فریم" | "عيّن_ضبابية_الإطار" | "קבע_טשטוש_פריים" | "فریم_بلر_مقرر_کرو" =>
3585 {
3586 let a = self.arg_num(&args, 0, 0.0)? as f32;
3587 let mut gfx = self.gfx.borrow_mut();
3588 gfx.frame_blur = a.clamp(0.0, 0.95);
3589 if gfx.frame_blur <= 0.0 {
3590 gfx.prev_frame = Vec::new();
3591 }
3592 return Ok(Value::Unit);
3593 },
3594
3595 "set_line_hue_cycle" | "ตั้งวนสีเส้น" | "تنظیم_چرخه_فام_خط" | "عيّن_دورة_صبغة_الخط" | "קבע_מחזור_גוון_קו" | "لائن_ہیو_سائیکل_مقرر_کرو" => {
3599 let rate = self.arg_num(&args, 0, 0.0)?;
3600 crate::runtime::set_line_hue_rate(rate);
3601 return Ok(Value::Unit);
3602 },
3603
3604 "set_color_space" | "ปริภูมิสี" | "色彩空间" | "色空間" | "색공간" | "تنظیم_فضای_رنگ" | "عيّن_فضاء_اللون" | "קבע_מרחב_צבע" | "کلر_اسپیس_مقرر_کرو" | "définir_espace_couleur" | "farbraum_setzen" | "задать_цветовое_пространство" =>
3608 {
3609 let m = self.arg_num(&args, 0, 0.0)? as i64;
3610 self.gfx.borrow_mut().linear_blend = m != 0;
3611 return Ok(Value::Unit);
3612 },
3613
3614 "set_gradient_space" | "ปริภูมิไล่สี" | "渐变空间" | "グラデ空間" | "그라데이션공간" | "تنظیم_فضای_گرادیان" | "عيّن_فضاء_التدرج" | "קבע_מרחב_גרדיאנט" | "گریڈینٹ_اسپیس_مقرر_کرو" | "définir_espace_dégradé" | "verlaufsraum_setzen" | "задать_пространство_градиента" =>
3617 {
3618 let m = self.arg_num(&args, 0, 1.0)? as i64;
3619 self.gfx.borrow_mut().grad_oklab = m != 0;
3620 return Ok(Value::Unit);
3621 },
3622
3623 "mix_color" | "ผสมสี" | "混合颜色" | "色混合" | "색혼합" | "ترکیب_رنگ" | "امزج_اللون" | "ערבב_צבע" | "رنگ_ملاؤ" | "mélanger_couleur" | "farbe_mischen" | "смешать_цвет" => {
3627 let c0 = rgb(
3628 self.arg_num(&args, 0, 0.0)?,
3629 self.arg_num(&args, 1, 0.0)?,
3630 self.arg_num(&args, 2, 0.0)?,
3631 );
3632 let c1 = rgb(
3633 self.arg_num(&args, 3, 255.0)?,
3634 self.arg_num(&args, 4, 255.0)?,
3635 self.arg_num(&args, 5, 255.0)?,
3636 );
3637 let t = self.arg_num(&args, 6, 0.5)? as f32;
3638 self.gfx.borrow_mut().color = crate::gfx::color::mix_oklab(c0, c1, t);
3639 return Ok(Value::Unit);
3640 },
3641
3642 "set_depth_test" | "ทดสอบความลึก" | "深度测试" | "深度テスト" | "깊이테스트" | "تنظیم_آزمون_عمق" | "عيّن_اختبار_العمق" | "קבע_בדיקת_עומק" | "ڈیپتھ_ٹیسٹ_مقرر_کرو" | "définir_test_profondeur" | "tiefentest_setzen" | "задать_тест_глубины" =>
3646 {
3647 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3648 self.gfx.borrow_mut().depth_test = on;
3649 return Ok(Value::Unit);
3650 },
3651
3652 "set_flat_shade" | "ตั้งแฟลตเชด" | "平面着色" | "フラット着色" | "평면음영" | "تنظیم_سایهپردازی_تخت" | "عيّن_تظليلا_مسطحا" | "קבע_הצללה_שטוחה" | "فلیٹ_شیڈ_مقرر_کرو" =>
3655 {
3656 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3657 self.gfx.borrow_mut().flat_shade = on;
3658 return Ok(Value::Unit);
3659 },
3660
3661 "set_normal_override" =>
3664 {
3665 let x = self.arg_num(&args, 0, 0.0)? as f32;
3666 let y = self.arg_num(&args, 1, -1.0)? as f32;
3667 let z = self.arg_num(&args, 2, 0.0)? as f32;
3668 self.gfx.borrow_mut().normal_override = Some([x, y, z]);
3669 return Ok(Value::Unit);
3670 },
3671
3672 "reset_normal_override" =>
3673 {
3674 self.gfx.borrow_mut().normal_override = None;
3675 return Ok(Value::Unit);
3676 },
3677
3678 "clear_depth" | "ล้างความลึก" | "清深度" | "深度クリア" | "깊이지우기" | "پاککردن_عمق" | "امسح_العمق" | "נקה_עומק" | "گہرائی_صاف_کرو" =>
3682 {
3683 self.gfx.borrow_mut().zbuf_needs_clear = true;
3684 return Ok(Value::Unit);
3685 },
3686
3687 "depth_blur" | "เบลอความลึก" | "dof" | "depth_of_field" | "景深" | "تاری_عمق" | "ضبابية_العمق" | "טשטוש_עומק" | "ڈیپتھ_بلر" =>
3693 {
3694 let focus = self.arg_num(&args, 0, 30.0)? as f32;
3695 let range = self.arg_num(&args, 1, 60.0)? as f32;
3696 let radius = self.arg_num(&args, 2, 3.0)?.max(0.0) as usize;
3697 let oil = self.arg_num(&args, 3, 0.0)? as f32;
3700 let mut gfx = self.gfx.borrow_mut();
3701 let w = gfx.width;
3702 let h = gfx.height;
3703 if gfx.depth_buf.len() == w * h {
3704 let g = &mut *gfx;
3705 crate::gfx::raster::depth_of_field(
3706 &mut g.buffer,
3707 &g.depth_buf,
3708 w,
3709 h,
3710 focus,
3711 range,
3712 radius,
3713 oil,
3714 );
3715 }
3716 return Ok(Value::Unit);
3717 },
3718
3719 "light_pool" | "แอ่งแสง" | "光池" | "ライトプール" | "빛웅덩이" | "برکه_نور" | "بركة_ضوء" | "בריכת_אור" | "روشنی_تالاب" | "bassin_lumière" | "lichtpfütze" | "лужа_света" =>
3725 {
3726 let x = self.arg_num(&args, 0, 0.0)? as f32;
3727 let y = self.arg_num(&args, 1, 0.0)? as f32;
3728 let z = self.arg_num(&args, 2, 0.0)? as f32;
3729 let radius = self.arg_num(&args, 3, 20.0)? as f32;
3730 let r = self.arg_num(&args, 4, 255.0)? as f32 / 255.0;
3731 let g = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3732 let b = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3733 let inten = self.arg_num(&args, 7, 1.0)? as f32;
3734 self.gfx
3735 .borrow_mut()
3736 .emit_light_pool(x, y, z, radius, [r, g, b], inten);
3737 return Ok(Value::Unit);
3738 },
3739
3740 "light_beam" | "ลำแสงไฟ" | "光柱" | "ライトビーム" | "빛기둥" | "پرتو_نور" | "شعاع_ضوء" | "קרן_אור" | "روشنی_شعاع" | "faisceau_lumière" | "lichtstrahl" | "луч_света" =>
3745 {
3746 let x = self.arg_num(&args, 0, 0.0)? as f32;
3747 let y = self.arg_num(&args, 1, 0.0)? as f32;
3748 let z = self.arg_num(&args, 2, 0.0)? as f32;
3749 let fy = self.arg_num(&args, 3, 0.0)? as f32;
3750 let radius = self.arg_num(&args, 4, 14.0)? as f32;
3751 let r = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3752 let g = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3753 let b = self.arg_num(&args, 7, 255.0)? as f32 / 255.0;
3754 let inten = self.arg_num(&args, 8, 1.0)? as f32;
3755 self.gfx
3756 .borrow_mut()
3757 .emit_light_beam(x, y, z, fy, radius, [r, g, b], inten);
3758 return Ok(Value::Unit);
3759 },
3760
3761 "grad_triangle" | "สามเหลี่ยมไล่สี" | "渐变三角" | "グラデ三角" | "그라데삼각" | "مثلث_گرادیان" | "مثلث_متدرج" | "משולש_גרדיאנט" | "گریڈینٹ_مثلث" | "triangle_dégradé" | "dreieck_verlauf" | "градиент_треугольник" =>
3765 {
3766 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3767 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3768 let c0 = rgb(
3769 self.arg_num(&args, 2, 255.0)?,
3770 self.arg_num(&args, 3, 255.0)?,
3771 self.arg_num(&args, 4, 255.0)?,
3772 );
3773 let x1 = self.arg_num(&args, 5, 0.0)? as f32;
3774 let y1 = self.arg_num(&args, 6, 0.0)? as f32;
3775 let c1 = rgb(
3776 self.arg_num(&args, 7, 255.0)?,
3777 self.arg_num(&args, 8, 255.0)?,
3778 self.arg_num(&args, 9, 255.0)?,
3779 );
3780 let x2 = self.arg_num(&args, 10, 0.0)? as f32;
3781 let y2 = self.arg_num(&args, 11, 0.0)? as f32;
3782 let c2 = rgb(
3783 self.arg_num(&args, 12, 255.0)?,
3784 self.arg_num(&args, 13, 255.0)?,
3785 self.arg_num(&args, 14, 255.0)?,
3786 );
3787 let mut gfx = self.gfx.borrow_mut();
3788 let (w, h, alpha, mode, lin, ok) = (
3789 gfx.width,
3790 gfx.height,
3791 gfx.alpha,
3792 gfx.blend,
3793 gfx.linear_blend,
3794 gfx.grad_oklab,
3795 );
3796 crate::gfx::raster::fill_triangle_grad(
3797 &mut gfx.buffer,
3798 w,
3799 h,
3800 alpha,
3801 mode,
3802 lin,
3803 ok,
3804 x0,
3805 y0,
3806 c0,
3807 x1,
3808 y1,
3809 c1,
3810 x2,
3811 y2,
3812 c2,
3813 );
3814 return Ok(Value::Unit);
3815 },
3816
3817 "grad_rect" | "สี่เหลี่ยมไล่สี" | "渐变矩形" | "グラデ矩形" | "그라데사각" | "مستطیل_گرادیان" | "مستطيل_متدرج" | "מלבן_גרדיאנט" | "گریڈینٹ_مستطیل" | "rectangle_dégradé" | "rechteck_verlauf" | "градиент_прямоугольник" =>
3820 {
3821 let x = self.arg_num(&args, 0, 0.0)? as f32;
3822 let y = self.arg_num(&args, 1, 0.0)? as f32;
3823 let rw = self.arg_num(&args, 2, 0.0)? as f32;
3824 let rh = self.arg_num(&args, 3, 0.0)? as f32;
3825 let c0 = rgb(
3826 self.arg_num(&args, 4, 255.0)?,
3827 self.arg_num(&args, 5, 255.0)?,
3828 self.arg_num(&args, 6, 255.0)?,
3829 );
3830 let c1 = rgb(
3831 self.arg_num(&args, 7, 0.0)?,
3832 self.arg_num(&args, 8, 0.0)?,
3833 self.arg_num(&args, 9, 0.0)?,
3834 );
3835 let dir = self.arg_num(&args, 10, 1.0)? as u8;
3836 let mut gfx = self.gfx.borrow_mut();
3837 let (w, h, alpha, mode, lin, ok) = (
3838 gfx.width,
3839 gfx.height,
3840 gfx.alpha,
3841 gfx.blend,
3842 gfx.linear_blend,
3843 gfx.grad_oklab,
3844 );
3845 crate::gfx::raster::fill_rect_grad(
3846 &mut gfx.buffer,
3847 w,
3848 h,
3849 alpha,
3850 mode,
3851 lin,
3852 ok,
3853 x,
3854 y,
3855 rw,
3856 rh,
3857 c0,
3858 c1,
3859 dir,
3860 );
3861 return Ok(Value::Unit);
3862 },
3863
3864 "shadow_blob" | "เงาวงรี" | "阴影斑" | "影ブロブ" | "그림자블롭" | "لکه_سایه" | "بقعة_ظل" | "כתם_צל" | "سایہ_دھبہ" | "tache_ombre" | "schattenklecks" | "пятно_тени" =>
3868 {
3869 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3870 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3871 let rx = self.arg_num(&args, 2, 16.0)? as f32;
3872 let ry = self.arg_num(&args, 3, 8.0)? as f32;
3873 let a = self.arg_num(&args, 4, 0.5)? as f32;
3874 let mut gfx = self.gfx.borrow_mut();
3875 let (w, h, color, soft, mode, lin) = (
3876 gfx.width,
3877 gfx.height,
3878 gfx.color,
3879 gfx.shadow.soft,
3880 gfx.blend,
3881 gfx.linear_blend,
3882 );
3883 crate::gfx::raster::fill_disc_soft(
3884 &mut gfx.buffer,
3885 w,
3886 h,
3887 cx,
3888 cy,
3889 rx,
3890 ry,
3891 color,
3892 a,
3893 soft,
3894 mode,
3895 lin,
3896 );
3897 return Ok(Value::Unit);
3898 },
3899
3900 "cast_shadow" | "ทอดเงา" | "投射阴影" | "影を落とす" | "그림자드리우기" | "افکندن_سایه" | "ألقِ_ظلا" | "הטל_צל" | "سایہ_ڈالو" | "projeter_ombre" | "schatten_werfen" | "отбросить_тень" =>
3905 {
3906 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3907 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3908 let height = (self.arg_num(&args, 2, 0.0)? as f32).max(0.0);
3909 let mut gfx = self.gfx.borrow_mut();
3910 let sp = gfx.shadow;
3911 let radius = (sp.base + sp.grow * height).max(0.5);
3912 let alpha = (sp.alpha - sp.fade * height).clamp(0.04, 1.0);
3913 let soft = (sp.soft + height * 0.004).clamp(0.0, 0.95);
3914 let (w, h, color, mode, lin) = (
3915 gfx.width,
3916 gfx.height,
3917 gfx.color,
3918 gfx.blend,
3919 gfx.linear_blend,
3920 );
3921 crate::gfx::raster::fill_disc_soft(
3922 &mut gfx.buffer,
3923 w,
3924 h,
3925 cx,
3926 cy,
3927 radius,
3928 radius * 0.62,
3929 color,
3930 alpha,
3931 soft,
3932 mode,
3933 lin,
3934 );
3935 return Ok(Value::Unit);
3936 },
3937
3938 "shadow_params" | "ตั้งค่าเงา" | "阴影参数" | "影設定" | "그림자설정" | "پارامترهای_سایه" | "معاملات_الظل" | "פרמטרי_צל" | "سایہ_پیرامیٹرز" | "paramètres_ombre" | "schattenparameter" | "параметры_тени" =>
3941 {
3942 let cur = self.gfx.borrow().shadow;
3943 let base = self.arg_num(&args, 0, cur.base as f64)? as f32;
3944 let grow = self.arg_num(&args, 1, cur.grow as f64)? as f32;
3945 let alpha = self.arg_num(&args, 2, cur.alpha as f64)? as f32;
3946 let fade = self.arg_num(&args, 3, cur.fade as f64)? as f32;
3947 let soft = self.arg_num(&args, 4, cur.soft as f64)? as f32;
3948 self.gfx.borrow_mut().shadow =
3949 crate::gfx::ShadowParams { base, grow, alpha, fade, soft };
3950 return Ok(Value::Unit);
3951 },
3952
3953 "depth_triangle" | "สามเหลี่ยมเรียงลึก" | "深度三角" | "深度三角形" | "깊이삼각" | "مثلث_عمق" | "مثلث_العمق" | "משולש_עומק" | "گہرائی_مثلث" | "triangle_profondeur" | "tiefendreieck" | "глубина_треугольник" =>
3958 {
3959 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3960 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3961 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3962 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3963 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
3964 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
3965 let z = self.arg_num(&args, 6, 0.0)? as f32;
3966 let mut gfx = self.gfx.borrow_mut();
3967 let color = gfx.color;
3968 gfx.depth_queue
3969 .push_triangle(z, color, x0, y0, x1, y1, x2, y2);
3970 return Ok(Value::Unit);
3971 },
3972
3973 "depth_line" | "เส้นเรียงลึก" | "深度线" | "深度線" | "깊이선" | "خط_عمق" | "خط_العمق" | "קו_עומק" | "گہرائی_لکیر" | "ligne_profondeur" | "tiefenlinie" | "глубина_линия" =>
3976 {
3977 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3978 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3979 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3980 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3981 let z = self.arg_num(&args, 4, 0.0)? as f32;
3982 let mut gfx = self.gfx.borrow_mut();
3983 let color = gfx.color;
3984 gfx.depth_queue.push_line(z, color, x0, y0, x1, y1);
3985 return Ok(Value::Unit);
3986 },
3987
3988 "เปิดหน้าต่าง" | "open_window" | "gfx_window" | "开窗" | "ウィンドウ開く" | "창열기" | "باز_کردن_پنجره" | "افتح_نافذة" | "פתח_חלון" | "ونڈو_کھولو" =>
3995 {
3996 let w = self.arg_num(&args, 0, 800.0)? as usize;
3997 let h = self.arg_num(&args, 1, 600.0)? as usize;
3998 #[cfg(not(target_arch = "wasm32"))]
3999 {
4000 let title = args
4001 .get(2)
4002 .map(|v| v.to_string())
4003 .unwrap_or_else(|| "Ling".into());
4004 let mut gfx = self.gfx.borrow_mut();
4005 let mut win = minifb::Window::new(
4006 &title,
4007 w,
4008 h,
4009 minifb::WindowOptions {
4010 resize: false,
4011 scale: minifb::Scale::X1,
4012 ..Default::default()
4013 },
4014 )
4015 .map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
4016 apply_frame_pacing(&mut win, gfx.vsync);
4017 gfx.buffer = vec![0u32; w * h];
4018 gfx.width = w;
4019 gfx.height = h;
4020 gfx.window = Some(win);
4021 gfx.topmost_window = false;
4022 gfx.sync_projection();
4023 }
4024 #[cfg(target_arch = "wasm32")]
4025 {
4026 let mut gfx = self.gfx.borrow_mut();
4027 gfx.width = w;
4028 gfx.height = h;
4029 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4031 crate::gfx::webgl::resize(w as u32, h as u32);
4032 }
4033 return Ok(Value::Unit);
4034 },
4035
4036 "เติม" | "fill" | "gfx_fill" | "clear" | "填" | "塗り潰し" | "채우기" | "清"
4038 | "消去" | "지우기" | "پر_کن" | "املأ" | "מלא" | "بھرو" => {
4039 let r = self.arg_num(&args, 0, 0.0)? as u32;
4040 let g = self.arg_num(&args, 1, 0.0)? as u32;
4041 let b = self.arg_num(&args, 2, 0.0)? as u32;
4042 #[cfg(not(target_arch = "wasm32"))]
4043 {
4044 let c = (r << 16) | (g << 8) | b;
4045 let mut gfx = self.gfx.borrow_mut();
4046 gfx.buffer.fill(c);
4047 gfx.zbuf_needs_clear = true; gfx.edge_set.clear(); }
4050 #[cfg(target_arch = "wasm32")]
4051 {
4052 let mut gfx = self.gfx.borrow_mut();
4053 gfx.fill_r = r as f32 / 255.0;
4054 gfx.fill_g = g as f32 / 255.0;
4055 gfx.fill_b = b as f32 / 255.0;
4056 let c = (r << 16) | (g << 8) | b;
4057 gfx.buffer.fill(c);
4058 gfx.zbuf_needs_clear = true;
4059 gfx.edge_set.clear();
4060 }
4061 return Ok(Value::Unit);
4062 },
4063
4064 "set_color_hsl" | "颜色HSL" | "色相" | "HSL色" | "HSL색설정" | "สีHSLวาด" | "تنظیم_رنگ_HSL" | "عيّن_اللون_HSL" | "קבע_צבע_HSL" | "HSL_رنگ_مقرر_کرو" | "définir_couleur_hsl" | "farbe_hsl_setzen" | "задать_цвет_hsl" =>
4067 {
4068 let h = self.arg_num(&args, 0, 0.0)?;
4069 let s = self.arg_num(&args, 1, 70.0)?;
4070 let l = self.arg_num(&args, 2, 50.0)?;
4071 let hex = hsl_to_hex(h, s, l);
4072 let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
4073 let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
4074 let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
4075 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4076 return Ok(Value::Unit);
4077 },
4078
4079 "สีดินสอ" | "set_color" | "gfx_color" | "color" | "设色" | "色設定" | "색설정" | "تنظیم_رنگ" | "عيّن_اللون" | "קבע_צבע" | "رنگ_مقرر_کرو" =>
4081 {
4082 let r = self.arg_num(&args, 0, 255.0)? as u32;
4083 let g = self.arg_num(&args, 1, 255.0)? as u32;
4084 let b = self.arg_num(&args, 2, 255.0)? as u32;
4085 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4086 return Ok(Value::Unit);
4087 },
4088
4089 "วาดสามเหลี่ยม"
4091 | "draw_triangle"
4092 | "gfx_triangle"
4093 | "triangle"
4094 | "画三角"
4095 | "三角形描画"
4096 | "삼각형그리기" | "رسم_مثلث" | "ارسم_مثلثا" | "צייר_משולש" | "مثلث_کھینچو" => {
4097 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4098 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4099 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4100 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4101 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
4102 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
4103 let mut gfx = self.gfx.borrow_mut();
4104 let color = gfx.color;
4105 #[cfg(not(target_arch = "wasm32"))]
4106 {
4107 let w = gfx.width;
4108 let h = gfx.height;
4109 fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
4110 }
4111 #[cfg(target_arch = "wasm32")]
4112 gfx.depth_queue
4113 .push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
4114 return Ok(Value::Unit);
4115 },
4116
4117 "วาดเส้น" | "draw_line" | "gfx_line" | "line" | "画线" | "線描く" | "선그리기" | "رسم_خط" | "ارسم_خط" | "צייר_קו" | "لکیر_کھینچو" =>
4119 {
4120 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4121 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4122 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4123 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4124 let mut gfx = self.gfx.borrow_mut();
4125 let color = gfx.color;
4126 #[cfg(not(target_arch = "wasm32"))]
4127 {
4128 let w = gfx.width;
4129 let h = gfx.height;
4130 let aa = gfx.antialias;
4131 let add = gfx.blend == 1;
4132 if aa {
4133 crate::gfx::raster::draw_line_aa(
4134 &mut gfx.buffer,
4135 w,
4136 h,
4137 color,
4138 add,
4139 x0,
4140 y0,
4141 x1,
4142 y1,
4143 );
4144 } else {
4145 draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
4146 }
4147 }
4148 #[cfg(target_arch = "wasm32")]
4149 gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
4150 return Ok(Value::Unit);
4151 },
4152
4153 "วาดจุด" | "draw_pixel" | "gfx_pixel" | "pixel" | "画点" | "点描く" | "점그리기" | "رسم_نقطه" | "ارسم_نقطة" | "צייר_פיקסל" | "پکسل_کھینچو" =>
4155 {
4156 let px = self.arg_num(&args, 0, 0.0)? as i32;
4157 let py = self.arg_num(&args, 1, 0.0)? as i32;
4158 #[cfg(not(target_arch = "wasm32"))]
4159 {
4160 let mut gfx = self.gfx.borrow_mut();
4161 let color = gfx.color;
4162 let w = gfx.width;
4163 let h = gfx.height;
4164 if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
4165 gfx.buffer[py as usize * w + px as usize] = color;
4166 }
4167 }
4168 #[cfg(target_arch = "wasm32")]
4169 {
4170 let mut gfx = self.gfx.borrow_mut();
4172 let color = gfx.color;
4173 let x = px as f32;
4174 let y = py as f32;
4175 gfx.depth_queue
4176 .push_triangle(0.0, color, x, y, x + 1.0, y, x + 1.0, y + 1.0);
4177 gfx.depth_queue
4178 .push_triangle(0.0, color, x, y, x + 1.0, y + 1.0, x, y + 1.0);
4179 }
4180 return Ok(Value::Unit);
4181 },
4182
4183 "แสดงผล" | "present" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시" | "نمایش" | "اعرض" | "הצג" | "دکھاؤ" =>
4185 {
4186 #[cfg(not(target_arch = "wasm32"))]
4199 let pre_update_mouse_down = self
4200 .gfx
4201 .borrow()
4202 .window
4203 .as_ref()
4204 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
4205 .unwrap_or(false);
4206 #[cfg(not(target_arch = "wasm32"))]
4207 {
4208 ling_fps_tick();
4209 ling_phase_frame();
4210 {
4212 let mut gfx = self.gfx.borrow_mut();
4213 if !gfx.depth_queue.is_empty() {
4214 let w = gfx.width;
4215 let h = gfx.height;
4216 let dt = gfx.depth_test;
4217 let reset_z = gfx.zbuf_needs_clear;
4218 let (bm, ba) = (gfx.blend, gfx.alpha);
4219 let aa = gfx.antialias;
4220 let queue = std::mem::take(&mut gfx.depth_queue);
4221 {
4222 let g = &mut *gfx;
4223 let z = if dt { Some(&mut g.depth_buf) } else { None };
4224 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4225 }
4226 gfx.depth_queue.set_state(bm, ba);
4227 gfx.zbuf_needs_clear = false;
4228 }
4229 let _t = std::time::Instant::now();
4230 if !gfx.post_done {
4231 gfx.toon_post_process();
4232 }
4233 gfx.post_done = false;
4234 ling_phase_add(phase::TOON, _t.elapsed().as_nanos());
4235 let w = gfx.width;
4236 let h = gfx.height;
4237 let g = &mut *gfx;
4238 if g.frame_blur > 0.0 {
4239 let a = (g.frame_blur.clamp(0.0, 0.995) * 256.0) as u32;
4244 if g.prev_frame.len() != g.buffer.len() {
4245 g.prev_frame = g.buffer.clone();
4246 }
4247 for (dst, prev) in g.buffer.iter_mut().zip(g.prev_frame.iter_mut()) {
4248 let c = *dst;
4249 let pv = *prev;
4250 let pr = (((pv >> 16) & 0xFF) * a) >> 8;
4251 let pg = (((pv >> 8) & 0xFF) * a) >> 8;
4252 let pb = ((pv & 0xFF) * a) >> 8;
4253 let cr = (c >> 16) & 0xFF;
4254 let cg = (c >> 8) & 0xFF;
4255 let cb = c & 0xFF;
4256 let outp = (cr.max(pr) << 16) | (cg.max(pg) << 8) | cb.max(pb);
4257 *dst = outp;
4258 *prev = outp;
4259 }
4260 }
4261 if let Some(win) = g.window.as_mut() {
4262 let _b = std::time::Instant::now();
4263 win.update_with_buffer(&g.buffer, w, h)
4264 .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
4265 ling_phase_add(phase::BLIT, _b.elapsed().as_nanos());
4266 }
4267 }
4268 let mouse_pos = {
4270 let gfx = self.gfx.borrow();
4271 gfx.window
4272 .as_ref()
4273 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
4274 };
4275 let mut gfx = self.gfx.borrow_mut();
4276 if gfx.mouse_captured {
4277 let w = gfx.width as f32;
4278 let h = gfx.height as f32;
4279 if let Some((mx, my)) = mouse_pos {
4280 if gfx.last_mx.is_nan() {
4281 gfx.mouse_dx = 0.0;
4282 gfx.mouse_dy = 0.0;
4283 gfx.last_mx = mx;
4284 gfx.last_my = my;
4285 } else {
4286 gfx.mouse_dx = mx - gfx.last_mx;
4287 gfx.mouse_dy = my - gfx.last_my;
4288 let margin = 6.0;
4291 let (mut nx, mut ny, mut warp) = (mx, my, false);
4292 if mx < margin {
4293 nx = w - margin - 2.0;
4294 warp = true;
4295 } else if mx > w - margin {
4296 nx = margin + 2.0;
4297 warp = true;
4298 }
4299 if my < margin {
4300 ny = h - margin - 2.0;
4301 warp = true;
4302 } else if my > h - margin {
4303 ny = margin + 2.0;
4304 warp = true;
4305 }
4306 if warp {
4307 #[cfg(windows)]
4308 unsafe {
4309 #[repr(C)]
4310 struct RECT {
4311 left: i32,
4312 top: i32,
4313 right: i32,
4314 bottom: i32,
4315 }
4316 extern "system" {
4317 fn GetForegroundWindow() -> isize;
4318 fn GetWindowRect(hwnd: isize, lpRect: *mut RECT)
4319 -> i32;
4320 fn SetCursorPos(x: i32, y: i32) -> i32;
4321 }
4322 let hwnd = GetForegroundWindow();
4323 let mut rect =
4324 RECT { left: 0, top: 0, right: 0, bottom: 0 };
4325 if GetWindowRect(hwnd, &mut rect) != 0 {
4326 SetCursorPos(
4327 rect.left + nx as i32,
4328 rect.top + ny as i32,
4329 );
4330 }
4331 }
4332 gfx.last_mx = nx;
4333 gfx.last_my = ny;
4334 } else {
4335 gfx.last_mx = mx;
4336 gfx.last_my = my;
4337 }
4338 }
4339 } else {
4340 gfx.mouse_dx = 0.0;
4341 gfx.mouse_dy = 0.0;
4342 }
4343 } else if let Some((mx, my)) = mouse_pos {
4344 if gfx.last_mx.is_nan() {
4345 gfx.mouse_dx = 0.0;
4346 gfx.mouse_dy = 0.0;
4347 } else {
4348 gfx.mouse_dx = mx - gfx.last_mx;
4349 gfx.mouse_dy = my - gfx.last_my;
4350 }
4351 gfx.last_mx = mx;
4352 gfx.last_my = my;
4353 } else {
4354 gfx.mouse_dx = 0.0;
4355 gfx.mouse_dy = 0.0;
4356 }
4357
4358 let is_active = gfx.window.as_mut().map(|w| w.is_active()).unwrap_or(true);
4366 if is_active && !gfx.was_active {
4367 gfx.focus_grace_frames = 5;
4368 #[cfg(windows)]
4371 if gfx.topmost_window {
4372 if let Some(w) = gfx.window.as_ref() {
4373 set_window_topmost(w.get_window_handle() as isize, true);
4374 }
4375 }
4376 } else if !is_active && gfx.was_active {
4377 #[cfg(windows)]
4383 if gfx.topmost_window {
4384 if let Some(w) = gfx.window.as_ref() {
4385 set_window_topmost(w.get_window_handle() as isize, false);
4386 }
4387 }
4388 }
4389 gfx.was_active = is_active;
4390 if gfx.focus_grace_frames > 0 {
4391 gfx.focus_grace_frames -= 1;
4392 }
4393 }
4394 #[cfg(target_arch = "wasm32")]
4395 {
4396 {
4397 let mut gfx = self.gfx.borrow_mut();
4401 let w = gfx.width;
4402 let h = gfx.height;
4403 if gfx.buffer.len() != w * h {
4404 gfx.buffer.resize(w * h, 0);
4405 }
4406 if !gfx.depth_queue.is_empty() {
4407 let dt = gfx.depth_test;
4408 let reset_z = gfx.zbuf_needs_clear;
4409 let aa = gfx.antialias;
4410 let queue = std::mem::take(&mut gfx.depth_queue);
4411 {
4412 let g = &mut *gfx;
4413 let z = if dt { Some(&mut g.depth_buf) } else { None };
4414 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4415 }
4416 gfx.zbuf_needs_clear = false;
4417 }
4418 if !gfx.post_done {
4419 gfx.toon_post_process();
4420 }
4421 gfx.post_done = false;
4422 crate::gfx::webgl::blit_rgb(&gfx.buffer, w, h);
4423 }
4424 self.wasm_pace_frame();
4425 }
4426 #[cfg(not(target_arch = "wasm32"))]
4430 {
4431 self.mouse_was_down = pre_update_mouse_down;
4432 }
4433 self.frame_num += 1;
4435 return Ok(Value::Unit);
4436 },
4437
4438 "เปิดหน้าต่างเต็มจอ"
4440 | "open_fullscreen"
4441 | "fullscreen"
4442 | "全屏"
4443 | "全画面"
4444 | "전체화면" | "باز_کردن_تمامصفحه" | "افتح_ملء_الشاشة" | "פתח_מסך_מלא" | "فل_سکرین_کھولو" => {
4445 #[cfg(target_arch = "wasm32")]
4448 let (default_w, default_h) = {
4449 let (cw, ch) = crate::gfx::webgl::canvas_size();
4450 (cw as f64, ch as f64)
4451 };
4452 #[cfg(all(not(target_arch = "wasm32"), windows))]
4454 let (default_w, default_h) = unsafe {
4455 extern "system" {
4456 fn GetSystemMetrics(nIndex: i32) -> i32;
4457 }
4458 (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
4459 };
4460 #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
4461 let (default_w, default_h) = native_screen_size();
4462
4463 let w = args
4464 .get(1)
4465 .map(|v| self.to_number(v).unwrap_or(default_w) as usize)
4466 .unwrap_or(default_w as usize);
4467 let h = args
4468 .get(2)
4469 .map(|v| self.to_number(v).unwrap_or(default_h) as usize)
4470 .unwrap_or(default_h as usize);
4471 #[cfg(not(target_arch = "wasm32"))]
4472 {
4473 let title = args
4474 .first()
4475 .map(|v| v.to_string())
4476 .unwrap_or_else(|| "Ling".into());
4477 let mut gfx = self.gfx.borrow_mut();
4478 let mut win = minifb::Window::new(
4479 &title,
4480 w,
4481 h,
4482 minifb::WindowOptions {
4483 borderless: true,
4484 title: false,
4485 resize: false,
4486 topmost: true,
4487 scale: minifb::Scale::X1,
4488 ..Default::default()
4489 },
4490 )
4491 .map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
4492 apply_frame_pacing(&mut win, gfx.vsync);
4493 #[cfg(windows)]
4495 let hwnd = win.get_window_handle() as isize;
4496 gfx.buffer = vec![0u32; w * h];
4497 gfx.width = w;
4498 gfx.height = h;
4499 gfx.window = Some(win);
4500 gfx.topmost_window = true;
4501 #[cfg(windows)]
4502 {
4503 gfx.hwnd = hwnd;
4504 }
4505 gfx.sync_projection();
4506 #[cfg(windows)]
4508 make_borderless_fullscreen(hwnd, w as i32, h as i32);
4509 #[cfg(windows)]
4510 force_window_focus(hwnd);
4511 }
4512 #[cfg(target_arch = "wasm32")]
4513 {
4514 let mut gfx = self.gfx.borrow_mut();
4515 gfx.width = w;
4516 gfx.height = h;
4517 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4519 crate::gfx::webgl::resize(w as u32, h as u32);
4520 }
4521 return Ok(Value::Unit);
4522 },
4523
4524 "get_width" | "ความกว้าง" | "宽" | "幅取得" | "너비" | "عرض" | "العرض" | "רוחב" | "چوڑائی" | "obtenir_largeur" | "breite_abrufen" | "получить_ширину" => {
4526 return Ok(Value::Number(self.gfx.borrow().width as f64));
4527 },
4528 "get_height" | "ความสูง" | "高" | "高取得" | "높이" | "ارتفاع" | "الارتفاع" | "גובה" | "اونچائی" | "obtenir_hauteur" | "höhe_abrufen" | "получить_высоту" => {
4529 return Ok(Value::Number(self.gfx.borrow().height as f64));
4530 },
4531
4532 "monitor_width" | "screen_width" | "屏宽" | "画面幅" | "화면너비" | "ความกว้างจอ" | "عرض_مانیتور" | "عرض_الشاشة" | "רוחב_צג" | "مانیٹر_چوڑائی" | "largeur_moniteur" | "monitor_breite" | "ширина_монитора" =>
4535 {
4536 return Ok(Value::Number(monitor_info().0 as f64));
4537 },
4538 "monitor_height" | "screen_height" | "屏高" | "画面高" | "화면높이" | "ความสูงจอ" | "ارتفاع_مانیتور" | "ارتفاع_الشاشة" | "גובה_צג" | "مانیٹر_اونچائی" | "hauteur_moniteur" | "monitor_höhe" | "высота_монитора" =>
4540 {
4541 return Ok(Value::Number(monitor_info().1 as f64));
4542 },
4543 "monitor_refresh"
4545 | "monitor_hz"
4546 | "monitor_fps"
4547 | "refresh_rate"
4548 | "刷新率"
4549 | "リフレッシュレート"
4550 | "주사율"
4551 | "อัตรารีเฟรช" | "نرخ_بروزرسانی_مانیتور" | "معدل_تحديث_الشاشة" | "קצב_רענון_צג" | "مانیٹر_ریفریش_ریٹ" | "fréquence_moniteur" | "monitor_bildwiederholrate" | "частота_монитора" => {
4552 return Ok(Value::Number(monitor_info().2 as f64));
4553 },
4554 "monitor_info" | "screen_info" | "屏幕信息" | "画面情報" | "화면정보" | "ข้อมูลจอ" | "اطلاعات_مانیتور" | "معلومات_الشاشة" | "מידע_צג" | "مانیٹر_معلومات" | "info_moniteur" | "bildschirminfo" | "инфо_монитора" =>
4556 {
4557 let (w, h, hz) = monitor_info();
4558 return Ok(Value::List(Rc::new(vec![
4559 Value::Number(w as f64),
4560 Value::Number(h as f64),
4561 Value::Number(hz as f64),
4562 ])));
4563 },
4564 "set_fps"
4566 | "set_target_fps"
4567 | "target_fps"
4568 | "设帧率"
4569 | "フレームレート設定"
4570 | "프레임설정"
4571 | "ตั้งเฟรมเรต" | "تنظیم_نرخ_فریم" | "عيّن_معدل_الإطارات" | "קבע_קצב_פריימים" | "ایف_پی_ایس_مقرر_کرو" | "définir_fps" | "fps_setzen" | "задать_fps" => {
4572 #[cfg(not(target_arch = "wasm32"))]
4573 {
4574 let fps = self.arg_num(&args, 0, 60.0)?.max(1.0) as usize;
4575 let mut gfx = self.gfx.borrow_mut();
4576 if let Some(win) = gfx.window.as_mut() {
4577 win.set_target_fps(fps);
4578 }
4579 }
4580 #[cfg(target_arch = "wasm32")]
4581 {
4582 self.wasm_target_fps = self.arg_num(&args, 0, 60.0)?.max(1.0);
4583 self.wasm_next_present_ms = 0.0;
4584 }
4585 return Ok(Value::Unit);
4586 },
4587
4588 "set_vsync" | "vsync" | "垂直同步" | "垂直同期" | "수직동기" | "ตั้งวีซิงก์" | "تنظیم_ویسینک" | "عيّن_تزامن_رأسي" | "קבע_וי_סינק" | "وی_سینک_مقرر_کرو" | "définir_vsync" | "vsync_setzen" | "задать_vsync" =>
4592 {
4593 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4594 #[cfg(not(target_arch = "wasm32"))]
4595 {
4596 let mut gfx = self.gfx.borrow_mut();
4597 gfx.vsync = on;
4598 if let Some(win) = gfx.window.as_mut() {
4599 apply_frame_pacing(win, on);
4600 }
4601 }
4602 #[cfg(target_arch = "wasm32")]
4603 {
4604 self.wasm_target_fps = if on { monitor_info().2 as f64 } else { 240.0 };
4605 self.wasm_next_present_ms = 0.0;
4606 }
4607 return Ok(Value::Unit);
4608 },
4609
4610 "หน้าต่างเปิดอยู่"
4612 | "window_is_open"
4613 | "gfx_is_open"
4614 | "is_open"
4615 | "窗开"
4616 | "開いている"
4617 | "창열림" | "پنجره_باز_است" | "النافذة_مفتوحة" | "החלון_פתוח" | "ونڈو_کھلی_ہے" => {
4618 #[cfg(not(target_arch = "wasm32"))]
4619 {
4620 let gfx = self.gfx.borrow();
4621 if gfx.want_quit {
4622 return Ok(Value::Bool(false));
4623 }
4624 #[cfg(windows)]
4630 let escape_down = if gfx.topmost_window {
4631 window_is_foreground(gfx.hwnd) && os_key_down(0x1B) } else {
4633 gfx.window
4634 .as_ref()
4635 .map(|w| w.is_key_down(minifb::Key::Escape))
4636 .unwrap_or(false)
4637 };
4638 #[cfg(not(windows))]
4639 let escape_down = gfx
4640 .window
4641 .as_ref()
4642 .map(|w| w.is_key_down(minifb::Key::Escape))
4643 .unwrap_or(false);
4644 let open = gfx.window.as_ref().map(|w| w.is_open()).unwrap_or(false)
4645 && !escape_down;
4646 return Ok(Value::Bool(open));
4647 }
4648 #[cfg(target_arch = "wasm32")]
4649 return Ok(Value::Bool(true));
4650 },
4651
4652 "quit" | "exit_game" | "close_window" => {
4655 #[cfg(not(target_arch = "wasm32"))]
4656 {
4657 self.gfx.borrow_mut().want_quit = true;
4658 }
4659 return Ok(Value::Unit);
4660 },
4661
4662 "key_down" | "กดค้าง" | "按键" | "キー押す" | "키누름" | "کلید_فشرده" | "المفتاح_مضغوط" | "מקש_לחוץ" | "بٹن_دبا_ہوا" | "touche_enfoncée" | "taste_gedrückt" | "клавиша_нажата" => {
4664 #[cfg(not(target_arch = "wasm32"))]
4665 {
4666 let name = self.arg_str(&args, 0, "");
4667 let mut gfx = self.gfx.borrow_mut();
4668 #[cfg(windows)]
4676 if gfx.topmost_window {
4677 if !window_is_foreground(gfx.hwnd) {
4678 return Ok(Value::Bool(false));
4679 }
4680 return Ok(Value::Bool(
4681 str_to_vk(&name).map(os_key_down).unwrap_or(false),
4682 ));
4683 }
4684 if gfx.input_suppressed() {
4685 return Ok(Value::Bool(false));
4686 }
4687 let down = gfx
4688 .window
4689 .as_ref()
4690 .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
4691 .unwrap_or(false);
4692 return Ok(Value::Bool(down));
4693 }
4694 #[cfg(target_arch = "wasm32")]
4695 {
4696 let name = self.arg_str(&args, 0, "");
4697 return Ok(Value::Bool(crate::gfx::wasm_is_key_down(&name)));
4698 }
4699 },
4700
4701 "key_pressed" | "กดปุ่ม" | "键按" | "キー押した" | "키눌림" | "فشردن_کلید" | "ضغط_المفتاح" | "לחיצת_מקש" | "بٹن_دبانا" | "touche_appuyée" | "taste_getippt" | "клавиша_нажатие" => {
4703 #[cfg(not(target_arch = "wasm32"))]
4704 {
4705 let name = self.arg_str(&args, 0, "");
4706 let pressed = {
4707 let mut gfx = self.gfx.borrow_mut();
4708 #[cfg(windows)]
4709 let topmost = gfx.topmost_window;
4710 #[cfg(not(windows))]
4711 let topmost = false;
4712 if topmost {
4713 #[cfg(windows)]
4714 {
4715 if !window_is_foreground(gfx.hwnd) {
4716 false
4717 } else {
4718 match str_to_vk(&name) {
4719 Some(vk) => {
4720 let idx = (vk as usize) & 0xFF;
4721 let down = os_key_down(vk);
4722 let was = gfx.raw_keys_prev[idx];
4723 gfx.raw_keys_prev[idx] = down;
4724 down && !was
4725 },
4726 None => false,
4727 }
4728 }
4729 }
4730 #[cfg(not(windows))]
4731 {
4732 false
4733 }
4734 } else if gfx.input_suppressed() {
4735 false
4736 } else {
4737 gfx.window
4738 .as_ref()
4739 .and_then(|w| {
4740 str_to_minifb_key(&name)
4741 .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No))
4742 })
4743 .unwrap_or(false)
4744 }
4745 };
4746 let pressed =
4748 pressed || ((name == "enter" || name == "return") && gamepad::start_edge());
4749 return Ok(Value::Bool(pressed));
4750 }
4751 #[cfg(target_arch = "wasm32")]
4752 {
4753 let name = self.arg_str(&args, 0, "");
4754 let pressed = crate::gfx::wasm_is_key_pressed(&name);
4755 return Ok(Value::Bool(pressed));
4756 }
4757 },
4758
4759 "mouse_dx" | "เมาส์X" | "鼠ΔX" | "マウスΔX" | "마우스ΔX" | "دلتا_ماوس_ایکس" | "فارق_الفأرة_س" | "דלתא_עכבר_X" | "ماؤس_ڈیلٹا_ایکس" | "souris_dx" | "maus_dx" | "мышь_dx" => {
4761 #[cfg(not(target_arch = "wasm32"))]
4762 return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
4763 #[cfg(target_arch = "wasm32")]
4764 return Ok(Value::Number(crate::gfx::wasm_mouse_dx() as f64));
4765 },
4766 #[cfg(not(target_arch = "wasm32"))]
4768 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" | "غلتک_ماوس" | "عجلة_الفأرة" | "גלגלת_עכבר" | "ماؤس_اسکرول" =>
4769 {
4770 let gfx = self.gfx.borrow();
4771 let s = gfx
4772 .window
4773 .as_ref()
4774 .and_then(|w| w.get_scroll_wheel())
4775 .map(|(_, y)| y as f64)
4776 .unwrap_or(0.0);
4777 return Ok(Value::Number(s));
4778 },
4779 #[cfg(target_arch = "wasm32")]
4780 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" | "غلتک_ماوس" | "عجلة_الفأرة" | "גלגלת_עכבר" | "ماؤس_اسکرول" =>
4781 {
4782 return Ok(Value::Number(0.0));
4783 },
4784 "mouse_dy" | "เมาส์Y" | "鼠ΔY" | "マウスΔY" | "마우스ΔY" | "دلتا_ماوس_ایگرگ" | "فارق_الفأرة_ص" | "דלתא_עכבר_Y" | "ماؤس_ڈیلٹا_وائی" | "souris_dy" | "maus_dy" | "мышь_dy" => {
4785 #[cfg(not(target_arch = "wasm32"))]
4786 return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
4787 #[cfg(target_arch = "wasm32")]
4788 return Ok(Value::Number(crate::gfx::wasm_mouse_dy() as f64));
4789 },
4790
4791 "pad_poll" | "手柄轮询" | "パッド更新" | "패드폴링" | "อัปเดตแพด" | "بررسی_دسته" | "استطلع_اليد" | "בדוק_בקר" | "گیم_پیڈ_پول" | "interroger_manette" | "gamepad_abfragen" | "опросить_геймпад" =>
4794 {
4795 #[cfg(not(target_arch = "wasm32"))]
4796 return Ok(Value::Number(self.pad_poll() as f64));
4797 #[cfg(target_arch = "wasm32")]
4798 return Ok(Value::Number(input_web::poll() as f64));
4799 },
4800 "pad_count" | "手柄数" | "パッド数" | "패드수" | "จำนวนแพด" | "تعداد_دسته" | "عدد_أيدي_التحكم" | "מספר_בקרים" | "گیم_پیڈ_تعداد" | "nombre_manettes" | "gamepad_anzahl" | "число_геймпадов" =>
4802 {
4803 #[cfg(not(target_arch = "wasm32"))]
4804 {
4805 let inp = self.input.borrow();
4806 let n = inp.as_ref().map_or(0, |s| s.sensorium.devices.count());
4807 return Ok(Value::Number(n as f64));
4808 }
4809 #[cfg(target_arch = "wasm32")]
4810 return Ok(Value::Number(input_web::count() as f64));
4811 },
4812 "pad_connected" | "手柄连接" | "パッド接続" | "패드연결" | "แพดเชื่อม" | "دسته_متصل" | "يد_التحكم_متصلة" | "בקר_מחובר" | "گیم_پیڈ_منسلک" | "manette_connectée" | "gamepad_verbunden" | "геймпад_подключён" =>
4814 {
4815 #[cfg(not(target_arch = "wasm32"))]
4816 {
4817 let i = self.arg_num(&args, 0, 0.0)? as usize;
4818 let inp = self.input.borrow();
4819 let c = inp
4820 .as_ref()
4821 .is_some_and(|s| s.sensorium.devices.for_player(i as u8).is_some());
4822 return Ok(Value::Bool(c));
4823 }
4824 #[cfg(target_arch = "wasm32")]
4825 {
4826 let i = self.arg_num(&args, 0, 0.0)? as usize;
4827 return Ok(Value::Bool(input_web::is_connected(i)));
4828 }
4829 },
4830 "pad_button" | "手柄按键" | "パッドボタン" | "패드버튼" | "ปุ่มแพด" | "دکمه_دسته" | "زر_اليد" | "כפתור_בקר" | "گیم_پیڈ_بٹن" | "bouton_manette" | "gamepad_taste" | "кнопка_геймпада" =>
4832 {
4833 #[cfg(not(target_arch = "wasm32"))]
4834 {
4835 let i = self.arg_num(&args, 0, 0.0)? as usize;
4836 let name = self.arg_str(&args, 1, "");
4837 let down = parse_pad_button(&name)
4838 .is_some_and(|b| self.with_pad(i, false, |p| p.is_down(b)));
4839 return Ok(Value::Bool(down));
4840 }
4841 #[cfg(target_arch = "wasm32")]
4842 {
4843 let i = self.arg_num(&args, 0, 0.0)? as usize;
4844 let name = self.arg_str(&args, 1, "");
4845 return Ok(Value::Bool(input_web::button_down(i, &name)));
4846 }
4847 },
4848 "pad_pressed" | "手柄按下" | "パッド押下" | "패드눌림" | "แพดกด" | "دکمه_دسته_فشرده" | "زر_اليد_مضغوط" | "כפתור_בקר_לחוץ" | "گیم_پیڈ_دبایا" | "manette_appuyée" | "gamepad_gedrückt" | "геймпад_нажат" =>
4851 {
4852 #[cfg(not(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 let p = parse_pad_button(&name)
4857 .is_some_and(|b| self.with_pad(i, false, |g| g.just_pressed(b)));
4858 return Ok(Value::Bool(p));
4859 }
4860 #[cfg(target_arch = "wasm32")]
4861 {
4862 let i = self.arg_num(&args, 0, 0.0)? as usize;
4863 let name = self.arg_str(&args, 1, "");
4864 return Ok(Value::Bool(input_web::button_down(i, &name)));
4865 }
4866 },
4867 "pad_lx" | "手柄左X" | "パッド左X" | "패드왼X" | "แพดซ้ายX" | "آنالوگ_چپ_ایکس" | "عصا_اليسرى_س" | "ג'ויסטיק_שמאל_X" | "بائیں_اسٹک_ایکس" | "manette_axe_gauche_x" | "gamepad_lx" | "геймпад_ось_лево_x" => {
4869 #[cfg(not(target_arch = "wasm32"))]
4870 {
4871 let i = self.arg_num(&args, 0, 0.0)? as usize;
4872 return Ok(Value::Number(
4873 self.with_pad(i, 0.0, |p| p.left_stick.x as f64),
4874 ));
4875 }
4876 #[cfg(target_arch = "wasm32")]
4877 {
4878 let i = self.arg_num(&args, 0, 0.0)? as usize;
4879 return Ok(Value::Number(input_web::axis_lx(i) as f64));
4880 }
4881 },
4882 "pad_ly" | "手柄左Y" | "パッド左Y" | "패드왼Y" | "แพดซ้ายY" | "آنالوگ_چپ_ایگرگ" | "عصا_اليسرى_ص" | "ג'ויסטיק_שמאל_Y" | "بائیں_اسٹک_وائی" | "manette_axe_gauche_y" | "gamepad_ly" | "геймпад_ось_лево_y" => {
4883 #[cfg(not(target_arch = "wasm32"))]
4884 {
4885 let i = self.arg_num(&args, 0, 0.0)? as usize;
4886 return Ok(Value::Number(
4887 self.with_pad(i, 0.0, |p| p.left_stick.y as f64),
4888 ));
4889 }
4890 #[cfg(target_arch = "wasm32")]
4891 {
4892 let i = self.arg_num(&args, 0, 0.0)? as usize;
4893 return Ok(Value::Number(input_web::axis_ly(i) as f64));
4894 }
4895 },
4896 "pad_rx" | "手柄右X" | "パッド右X" | "패드오X" | "แพดขวาX" | "آنالوگ_راست_ایکس" | "عصا_اليمنى_س" | "ג'ויסטיק_ימין_X" | "دائیں_اسٹک_ایکس" | "manette_axe_droit_x" | "gamepad_rx" | "геймпад_ось_право_x" => {
4897 #[cfg(not(target_arch = "wasm32"))]
4898 {
4899 let i = self.arg_num(&args, 0, 0.0)? as usize;
4900 return Ok(Value::Number(
4901 self.with_pad(i, 0.0, |p| p.right_stick.x as f64),
4902 ));
4903 }
4904 #[cfg(target_arch = "wasm32")]
4905 {
4906 let i = self.arg_num(&args, 0, 0.0)? as usize;
4907 return Ok(Value::Number(input_web::axis_rx(i) as f64));
4908 }
4909 },
4910 "pad_ry" | "手柄右Y" | "パッド右Y" | "패드오Y" | "แพดขวาY" | "آنالوگ_راست_ایگرگ" | "عصا_اليمنى_ص" | "ג'ויסטיק_ימין_Y" | "دائیں_اسٹک_وائی" | "manette_axe_droit_y" | "gamepad_ry" | "геймпад_ось_право_y" => {
4911 #[cfg(not(target_arch = "wasm32"))]
4912 {
4913 let i = self.arg_num(&args, 0, 0.0)? as usize;
4914 return Ok(Value::Number(
4915 self.with_pad(i, 0.0, |p| p.right_stick.y as f64),
4916 ));
4917 }
4918 #[cfg(target_arch = "wasm32")]
4919 {
4920 let i = self.arg_num(&args, 0, 0.0)? as usize;
4921 return Ok(Value::Number(input_web::axis_ry(i) as f64));
4922 }
4923 },
4924 "pad_lt" | "手柄左扳机" | "パッド左トリガー" | "패드왼트리거" | "ไกแพดซ้าย" | "ماشه_چپ" | "زناد_اليسار" | "הדק_שמאל" | "بائیں_ٹریگر" | "manette_gâchette_gauche" | "gamepad_lt" | "геймпад_триггер_лево" =>
4926 {
4927 #[cfg(not(target_arch = "wasm32"))]
4928 {
4929 let i = self.arg_num(&args, 0, 0.0)? as usize;
4930 return Ok(Value::Number(
4931 self.with_pad(i, 0.0, |p| p.left_trigger as f64),
4932 ));
4933 }
4934 #[cfg(target_arch = "wasm32")]
4935 {
4936 let i = self.arg_num(&args, 0, 0.0)? as usize;
4937 return Ok(Value::Number(input_web::trigger_lt(i) as f64));
4938 }
4939 },
4940 "pad_rt" | "手柄右扳机" | "パッド右トリガー" | "패드오트리거" | "ไกแพดขวา" | "ماشه_راست" | "زناد_اليمين" | "הדק_ימין" | "دائیں_ٹریگر" | "manette_gâchette_droite" | "gamepad_rt" | "геймпад_триггер_право" =>
4941 {
4942 #[cfg(not(target_arch = "wasm32"))]
4943 {
4944 let i = self.arg_num(&args, 0, 0.0)? as usize;
4945 return Ok(Value::Number(
4946 self.with_pad(i, 0.0, |p| p.right_trigger as f64),
4947 ));
4948 }
4949 #[cfg(target_arch = "wasm32")]
4950 {
4951 let i = self.arg_num(&args, 0, 0.0)? as usize;
4952 return Ok(Value::Number(input_web::trigger_rt(i) as f64));
4953 }
4954 },
4955 "pad_rumble" | "手柄震动" | "パッド振動" | "패드진동" | "แพดสั่น" | "لرزش_دسته" | "اهتزاز_اليد" | "רטט_בקר" | "گیم_پیڈ_تھرتھراہٹ" | "vibration_manette" | "gamepad_vibration" | "вибрация_геймпада" =>
4957 {
4958 #[cfg(not(target_arch = "wasm32"))]
4959 {
4960 use ling_input::backend::InputBackend;
4961 let i = self.arg_num(&args, 0, 0.0)? as usize;
4962 let lo = self.arg_num(&args, 1, 0.0)? as f32;
4963 let hi = self.arg_num(&args, 2, lo as f64)? as f32;
4964 let mut inp = self.input.borrow_mut();
4965 if let Some(s) = inp.as_mut() {
4966 if let Some(dev) = s.sensorium.devices.for_player(i as u8).map(|d| d.id) {
4967 s.backend.set_rumble(
4968 dev,
4969 ling_input::Rumble { low: lo, high: hi, ..Default::default() },
4970 );
4971 }
4972 }
4973 return Ok(Value::Unit);
4974 }
4975 #[cfg(target_arch = "wasm32")]
4976 return Ok(Value::Unit);
4977 },
4978
4979 "set_camera_pos" | "ตั้งตำแหน่งกล้อง" | "镜坐标" | "カメラ座標" | "카메라좌표" | "تنظیم_موقعیت_دوربین" | "عيّن_موضع_الكاميرا" | "קבע_מיקום_מצלמה" | "کیمرہ_مقام_مقرر_کرو" | "définir_position_caméra" | "kameraposition_setzen" | "задать_позицию_камеры" =>
4981 {
4982 let x = self.arg_num(&args, 0, 0.0)? as f32;
4983 let y = self.arg_num(&args, 1, 0.0)? as f32;
4984 let z = self.arg_num(&args, 2, 0.0)? as f32;
4985 {
4986 let mut gfx = self.gfx.borrow_mut();
4987 gfx.camera.tx = x;
4988 gfx.camera.ty = y;
4989 gfx.camera.tz = z;
4990 }
4991 #[cfg(not(target_arch = "wasm32"))]
4992 if let Some(audio) = &self.audio {
4993 audio.set_listener_pos(x, y, z);
4994 }
4995 return Ok(Value::Unit);
4996 },
4997
4998 "move_camera" => {
5000 let dx = self.arg_num(&args, 0, 0.0)? as f32;
5001 let dy = self.arg_num(&args, 1, 0.0)? as f32;
5002 let dz = self.arg_num(&args, 2, 0.0)? as f32;
5003 let mut gfx = self.gfx.borrow_mut();
5004 gfx.camera.tx += dx;
5005 gfx.camera.ty += dy;
5006 gfx.camera.tz += dz;
5007 return Ok(Value::Unit);
5008 },
5009
5010 "set_zdist" | "ตั้งระยะห่าง" | "镜距" | "Z距離設定" | "Z거리설정" | "تنظیم_فاصله_عمق" | "عيّن_مسافة_العمق" | "קבע_מרחק_עומק" | "گہرائی_فاصلہ_مقرر_کرو" | "définir_distance_z" | "z_abstand_setzen" | "задать_дистанцию_z" =>
5012 {
5013 let d = self.arg_num(&args, 0, 5.0)? as f32;
5014 self.gfx.borrow_mut().camera.zdist = d;
5015 return Ok(Value::Unit);
5016 },
5017
5018 "capture_mouse" | "จับเมาส์" | "捕鼠" | "マウス捕捉" | "마우스잡기" | "ضبط_ماوس" | "امسك_الفأرة" | "לכוד_עכבר" | "ماؤس_پکڑو" | "capturer_souris" | "maus_erfassen" | "захватить_мышь" =>
5020 {
5021 #[cfg(not(target_arch = "wasm32"))]
5022 {
5023 let mut gfx = self.gfx.borrow_mut();
5024 gfx.mouse_captured = true;
5025 gfx.last_mx = f32::NAN;
5026 if let Some(win) = gfx.window.as_mut() {
5027 win.set_cursor_visibility(false);
5028 }
5029 }
5030 return Ok(Value::Unit);
5031 },
5032
5033 "release_mouse" => {
5035 #[cfg(not(target_arch = "wasm32"))]
5036 {
5037 let mut gfx = self.gfx.borrow_mut();
5038 gfx.mouse_captured = false;
5039 gfx.last_mx = f32::NAN;
5040 if let Some(win) = gfx.window.as_mut() {
5041 win.set_cursor_visibility(true);
5042 }
5043 #[cfg(windows)]
5044 unsafe {
5045 extern "system" {
5047 fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
5048 }
5049 ClipCursor(std::ptr::null());
5050 }
5051 }
5052 return Ok(Value::Unit);
5053 },
5054
5055 "cursor_hide" => {
5061 #[cfg(not(target_arch = "wasm32"))]
5062 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5063 win.set_cursor_visibility(false);
5064 }
5065 return Ok(Value::Unit);
5066 },
5067 "cursor_show" => {
5068 #[cfg(not(target_arch = "wasm32"))]
5069 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5070 win.set_cursor_visibility(true);
5071 }
5072 return Ok(Value::Unit);
5073 },
5074
5075 "set_camera" | "ตั้งกล้อง" | "设镜" | "设置摄像机" | "カメラ設定" | "카메라설정" | "تنظیم_دوربین" | "عيّن_الكاميرا" | "קבע_מצלמה" | "کیمرہ_مقرر_کرو" | "définir_caméra" | "kamera_setzen" | "задать_камеру" =>
5082 {
5083 let cry = self.arg_num(&args, 0, 1.0)? as f32;
5084 let sry = self.arg_num(&args, 1, 0.0)? as f32;
5085 let crx = self.arg_num(&args, 2, 1.0)? as f32;
5086 let srx = self.arg_num(&args, 3, 0.0)? as f32;
5087 let mut gfx = self.gfx.borrow_mut();
5088 gfx.camera.cry = cry;
5089 gfx.camera.sry = sry;
5090 gfx.camera.crx = crx;
5091 gfx.camera.srx = srx;
5092 return Ok(Value::Unit);
5093 },
5094
5095 "set_projection" | "ตั้งโปรเจกชัน" | "投影" | "投影設定" | "투영설정" | "تنظیم_فرافکنی" | "عيّن_الإسقاط" | "קבע_הטלה" | "پروجیکشن_مقرر_کرو" | "définir_projection" | "projektion_setzen" | "задать_проекцию" =>
5098 {
5099 let cx = self.arg_num(&args, 0, 960.0)? as f32;
5100 let cy = self.arg_num(&args, 1, 540.0)? as f32;
5101 let focal = self.arg_num(&args, 2, 1080.0)? as f32;
5102 let zdist = self.arg_num(&args, 3, 5.0)? as f32;
5103 let mut gfx = self.gfx.borrow_mut();
5104 gfx.camera.cx = cx;
5105 gfx.camera.cy = cy;
5106 gfx.camera.focal = focal;
5107 gfx.camera.zdist = zdist;
5108 return Ok(Value::Unit);
5109 },
5110
5111 "gltf_load" => {
5113 let path = self.arg_str(&args, 0, "");
5114 match ling_physics::gltf::GltfModel::load(&path) {
5115 Ok(m) => {
5116 self.gltf_models.borrow_mut().push(m);
5117 let h = self.gltf_models.borrow().len() - 1;
5118 return Ok(Value::Number(h as f64));
5119 }
5120 Err(e) => {
5121 eprintln!("mesh_load failed ({path}): {e}");
5122 return Ok(Value::Number(-1.0));
5123 }
5124 }
5125 },
5126 "gltf_anim_count" => {
5128 let h = self.arg_num(&args, 0, -1.0)? as i64;
5129 let n = self
5130 .gltf_models
5131 .borrow()
5132 .get(h as usize)
5133 .map(|m| m.animations.len())
5134 .unwrap_or(0);
5135 return Ok(Value::Number(n as f64));
5136 },
5137 "gltf_anim_name" => {
5139 let h = self.arg_num(&args, 0, -1.0)? as i64;
5140 let i = self.arg_num(&args, 1, 0.0)? as usize;
5141 let s = self
5142 .gltf_models
5143 .borrow()
5144 .get(h as usize)
5145 .and_then(|m| m.animations.get(i))
5146 .map(|a| a.name.clone())
5147 .unwrap_or_default();
5148 return Ok(Value::Str(s));
5149 },
5150 "gltf_anim_dur" => {
5152 let h = self.arg_num(&args, 0, -1.0)? as i64;
5153 let i = self.arg_num(&args, 1, 0.0)? as usize;
5154 let d = self
5155 .gltf_models
5156 .borrow()
5157 .get(h as usize)
5158 .and_then(|m| m.animations.get(i))
5159 .map(|a| a.duration)
5160 .unwrap_or(0.0);
5161 return Ok(Value::Number(d as f64));
5162 },
5163 "gltf_tris" => {
5165 let h = self.arg_num(&args, 0, -1.0)? as i64;
5166 let n: usize = self
5167 .gltf_models
5168 .borrow()
5169 .get(h as usize)
5170 .map(|m| m.meshes.iter().map(|mm| mm.indices.len()).sum::<usize>() / 3)
5171 .unwrap_or(0);
5172 return Ok(Value::Number(n as f64));
5173 },
5174
5175 "gltf_joint_count" => {
5177 let h = self.arg_num(&args, 0, -1.0)? as i64;
5178 let n = self
5179 .gltf_models
5180 .borrow()
5181 .get(h as usize)
5182 .and_then(|m| m.skins.first())
5183 .map(|s| s.joints.len())
5184 .unwrap_or(0);
5185 return Ok(Value::Number(n as f64));
5186 },
5187 "gltf_joint_name" => {
5189 let h = self.arg_num(&args, 0, -1.0)? as i64;
5190 let j = self.arg_num(&args, 1, 0.0)? as usize;
5191 let models = self.gltf_models.borrow();
5192 let s = models
5193 .get(h as usize)
5194 .and_then(|m| {
5195 m.skins
5196 .first()
5197 .and_then(|sk| sk.joints.get(j))
5198 .and_then(|jt| m.nodes.get(jt.node_idx))
5199 .map(|n| n.name.clone())
5200 })
5201 .unwrap_or_default();
5202 return Ok(Value::Str(s));
5203 },
5204
5205 "gltf_draw" => {
5210 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5211 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5212 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5213 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5214 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5215 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5216 let (sy, cyy) = yaw.sin_cos();
5217 let models = self.gltf_models.borrow();
5218 let model = match models.get(hh as usize) {
5219 Some(m) => m,
5220 None => return Ok(Value::Unit),
5221 };
5222 let mut gfx = self.gfx.borrow_mut();
5223 let cp = {
5224 let c = &gfx.camera;
5225 ling_gpu::CameraParams {
5226 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5227 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5228 tx: c.tx, ty: c.ty, tz: c.tz,
5229 }
5230 };
5231 let near = -gfx.camera.zdist + 0.02;
5232 let ambient = gfx.ambient;
5233 for mesh in &model.meshes {
5234 let nlow = mesh.name.to_lowercase();
5235 let base: u32 = if nlow.contains("hair") {
5236 0x7a4a28
5237 } else if nlow.contains("cloth") || nlow.contains("top") {
5238 0x4a86e0
5239 } else if nlow.contains("wing") {
5240 0xe6ecf5
5241 } else if nlow.contains("star") {
5242 0xffd24d
5243 } else {
5244 0xf2d6b8
5245 };
5246 let nv = mesh.verts.len();
5247 if nv == 0 {
5248 continue;
5249 }
5250 let mut world = vec![0.0f32; nv * 3];
5251 for (i, v) in mesh.verts.iter().enumerate() {
5252 let gx = v.pos.x * scale;
5253 let gy = -v.pos.y * scale;
5254 let gz = -v.pos.z * scale;
5255 let rx = gx * cyy + gz * sy;
5256 let rz = -gx * sy + gz * cyy;
5257 world[i * 3] = ox + rx;
5258 world[i * 3 + 1] = oy + gy;
5259 world[i * 3 + 2] = oz + rz;
5260 }
5261 let mut proj = vec![0.0f32; nv * 3];
5262 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5263 let idx = &mesh.indices;
5264 let nt = idx.len() / 3;
5265 for t in 0..nt {
5266 let ia = idx[t * 3] as usize;
5267 let ib = idx[t * 3 + 1] as usize;
5268 let ic = idx[t * 3 + 2] as usize;
5269 if ia >= nv || ib >= nv || ic >= nv {
5270 continue;
5271 }
5272 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5273 if (da + db + dc) / 3.0 <= near {
5274 continue;
5275 }
5276 let col = {
5277 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5278 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5279 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5280 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5281 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5282 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5283 let centroid =
5284 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5285 if gfx.flat_shade {
5286 base
5287 } else {
5288 crate::gfx::light::compute_lit_color(
5289 base, normal, centroid, &gfx.lights, ambient,
5290 )
5291 }
5292 };
5293 let depth = (da + db + dc) / 3.0;
5294 let col = gfx.fog_apply(col, depth);
5295 gfx.depth_queue.push_triangle_zv(
5296 col,
5297 proj[ia * 3], proj[ia * 3 + 1], da,
5298 proj[ib * 3], proj[ib * 3 + 1], db,
5299 proj[ic * 3], proj[ic * 3 + 1], dc,
5300 );
5301 }
5302 }
5303 return Ok(Value::Unit);
5304 },
5305
5306 "gltf_autorig" => {
5308 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5309 let mut models = self.gltf_models.borrow_mut();
5310 if let Some(m) = models.get_mut(hh as usize) {
5311 return Ok(Value::Number(m.autorig() as f64));
5312 }
5313 return Ok(Value::Number(0.0));
5314 },
5315
5316 "gltf_pose_draw" => {
5320 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5321 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5322 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5323 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5324 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5325 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5326 let euler: Vec<f32> = match args.get(6) {
5327 Some(Value::List(v)) => {
5328 v.iter().map(|x| self.to_number(x).unwrap_or(0.0) as f32).collect()
5329 },
5330 _ => Vec::new(),
5331 };
5332 let (sy, cyy) = yaw.sin_cos();
5333 let models = self.gltf_models.borrow();
5334 let model = match models.get(hh as usize) {
5335 Some(m) => m,
5336 None => return Ok(Value::Unit),
5337 };
5338 let skinned = model.skin_local(&euler);
5339 let mut gfx = self.gfx.borrow_mut();
5340 let cp = {
5341 let c = &gfx.camera;
5342 ling_gpu::CameraParams {
5343 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5344 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5345 tx: c.tx, ty: c.ty, tz: c.tz,
5346 }
5347 };
5348 let near = -gfx.camera.zdist + 0.02;
5349 let ambient = gfx.ambient;
5350 for (mi, mesh) in model.meshes.iter().enumerate() {
5351 let nlow = mesh.name.to_lowercase();
5352 let base: u32 = if nlow.contains("hair") {
5353 0x7a4a28
5354 } else if nlow.contains("cloth") || nlow.contains("top") {
5355 0x4a86e0
5356 } else if nlow.contains("wing") {
5357 0xe6ecf5
5358 } else if nlow.contains("star") {
5359 0xffd24d
5360 } else {
5361 0xf2d6b8
5362 };
5363 let sk = match skinned.get(mi) {
5364 Some(s) => s,
5365 None => continue,
5366 };
5367 let nv = sk.len();
5368 if nv == 0 {
5369 continue;
5370 }
5371 let mut world = vec![0.0f32; nv * 3];
5372 for i in 0..nv {
5373 let gx = sk[i][0] * scale;
5374 let gy = -sk[i][1] * scale;
5375 let gz = -sk[i][2] * scale;
5376 let rx = gx * cyy + gz * sy;
5377 let rz = -gx * sy + gz * cyy;
5378 world[i * 3] = ox + rx;
5379 world[i * 3 + 1] = oy + gy;
5380 world[i * 3 + 2] = oz + rz;
5381 }
5382 let mut proj = vec![0.0f32; nv * 3];
5383 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5384 let idx = &mesh.indices;
5385 let nt = idx.len() / 3;
5386 for t in 0..nt {
5387 let ia = idx[t * 3] as usize;
5388 let ib = idx[t * 3 + 1] as usize;
5389 let ic = idx[t * 3 + 2] as usize;
5390 if ia >= nv || ib >= nv || ic >= nv {
5391 continue;
5392 }
5393 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5394 if (da + db + dc) / 3.0 <= near {
5395 continue;
5396 }
5397 let col = {
5398 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5399 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5400 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5401 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5402 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5403 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5404 let centroid =
5405 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5406 if gfx.flat_shade {
5407 base
5408 } else {
5409 crate::gfx::light::compute_lit_color(
5410 base, normal, centroid, &gfx.lights, ambient,
5411 )
5412 }
5413 };
5414 let depth = (da + db + dc) / 3.0;
5415 let col = gfx.fog_apply(col, depth);
5416 gfx.depth_queue.push_triangle_zv(
5417 col,
5418 proj[ia * 3], proj[ia * 3 + 1], da,
5419 proj[ib * 3], proj[ib * 3 + 1], db,
5420 proj[ic * 3], proj[ic * 3 + 1], dc,
5421 );
5422 }
5423 }
5424 return Ok(Value::Unit);
5425 },
5426
5427 "draw_mesh" | "วาดเมช" | "رسم_مش" | "ارسم_شبكة" | "צייר_רשת" | "میش_کھینچو" | "dessiner_maillage" | "netz_zeichnen" | "рисовать_меш" => {
5434 let pos = match args.first() {
5435 Some(Value::List(v)) => v,
5436 _ => return Ok(Value::Unit),
5437 };
5438 let idx = match args.get(1) {
5439 Some(Value::List(v)) => v,
5440 _ => return Ok(Value::Unit),
5441 };
5442 let ox = self.arg_num(&args, 2, 0.0)? as f32;
5443 let oy = self.arg_num(&args, 3, 0.0)? as f32;
5444 let oz = self.arg_num(&args, 4, 0.0)? as f32;
5445 let scale = self.arg_num(&args, 5, 1.0)? as f32;
5446 let mode = self.arg_num(&args, 6, 0.0)? as i64;
5447 let nv = pos.len() / 3;
5448 if nv == 0 {
5449 return Ok(Value::Unit);
5450 }
5451 let mut world = vec![0.0f32; nv * 3];
5452 for i in 0..nv {
5453 world[i * 3] = ox + self.to_number(&pos[i * 3]).unwrap_or(0.0) as f32 * scale;
5454 world[i * 3 + 1] =
5455 oy + self.to_number(&pos[i * 3 + 1]).unwrap_or(0.0) as f32 * scale;
5456 world[i * 3 + 2] =
5457 oz + self.to_number(&pos[i * 3 + 2]).unwrap_or(0.0) as f32 * scale;
5458 }
5459 let mut gfx = self.gfx.borrow_mut();
5460 let cp = {
5461 let c = &gfx.camera;
5462 ling_gpu::CameraParams {
5463 cry: c.cry,
5464 sry: c.sry,
5465 crx: c.crx,
5466 srx: c.srx,
5467 cx: c.cx,
5468 cy: c.cy,
5469 focal: c.focal,
5470 zdist: c.zdist,
5471 tx: c.tx,
5472 ty: c.ty,
5473 tz: c.tz,
5474 }
5475 };
5476 let near = -gfx.camera.zdist + 0.02;
5477 let base = gfx.color;
5478 let ambient = gfx.ambient;
5479 let mut proj = vec![0.0f32; nv * 3]; ling_gpu::backend().project_points(&world, &cp, &mut proj);
5481 let nt = idx.len() / 3;
5482 for t in 0..nt {
5483 let ia = self.to_number(&idx[t * 3]).unwrap_or(0.0) as usize;
5484 let ib = self.to_number(&idx[t * 3 + 1]).unwrap_or(0.0) as usize;
5485 let ic = self.to_number(&idx[t * 3 + 2]).unwrap_or(0.0) as usize;
5486 if ia >= nv || ib >= nv || ic >= nv {
5487 continue;
5488 }
5489 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5490 if (da + db + dc) / 3.0 <= near {
5491 continue;
5492 } let col = if mode == 1 {
5494 let h = t as f32 * 0.6;
5495 let r = ((h.sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5496 let g = (((h + 2.094).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5497 let b = (((h + 4.189).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5498 (r << 16) | (g << 8) | b
5499 } else {
5500 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5501 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5502 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5503 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5504 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5505 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5506 let centroid = [
5507 (ax + bx + px) / 3.0,
5508 (ay + by + py) / 3.0,
5509 (az + bz + pz) / 3.0,
5510 ];
5511 if gfx.flat_shade {
5512 base
5513 } else {
5514 crate::gfx::light::compute_lit_color(
5515 base,
5516 normal,
5517 centroid,
5518 &gfx.lights,
5519 ambient,
5520 )
5521 }
5522 };
5523 let depth = (da + db + dc) / 3.0;
5524 let col = gfx.fog_apply(col, depth);
5525 gfx.depth_queue.push_triangle_zv(
5529 col,
5530 proj[ia * 3],
5531 proj[ia * 3 + 1],
5532 da,
5533 proj[ib * 3],
5534 proj[ib * 3 + 1],
5535 db,
5536 proj[ic * 3],
5537 proj[ic * 3 + 1],
5538 dc,
5539 );
5540 }
5541 return Ok(Value::Unit);
5542 },
5543
5544 "add_light" | "เพิ่มแสง" | "加灯" | "ライト追加" | "조명추가" | "افزودن_نور" | "أضف_ضوء" | "הוסף_אור" | "روشنی_شامل_کرو" | "ajouter_lumière" | "licht_hinzufügen" | "добавить_свет" =>
5548 {
5549 let x = self.arg_num(&args, 0, 0.0)? as f32;
5550 let y = self.arg_num(&args, 1, -3.0)? as f32;
5551 let z = self.arg_num(&args, 2, 3.0)? as f32;
5552 let mut r = self.arg_num(&args, 3, 1.0)? as f32;
5553 let mut g = self.arg_num(&args, 4, 1.0)? as f32;
5554 let mut b = self.arg_num(&args, 5, 1.0)? as f32;
5555 if r > 1.5 || g > 1.5 || b > 1.5 {
5558 r /= 255.0;
5559 g /= 255.0;
5560 b /= 255.0;
5561 }
5562 let intensity = self.arg_num(&args, 6, 1.0)? as f32;
5563 let radius = self.arg_num(&args, 7, 0.0)? as f32;
5564 self.gfx
5565 .borrow_mut()
5566 .lights
5567 .push(Light { x, y, z, r, g, b, intensity, radius });
5568 return Ok(Value::Unit);
5569 },
5570
5571 "clear_lights" | "ล้างแสง" | "清灯" | "ライト消去" | "조명초기화" | "پاککردن_نورها" | "امسح_الأضواء" | "נקה_אורות" | "روشنیاں_صاف_کرو" | "effacer_lumières" | "lichter_löschen" | "очистить_свет" =>
5573 {
5574 self.gfx.borrow_mut().lights.clear();
5575 return Ok(Value::Unit);
5576 },
5577
5578 "set_material" | "ตั้งวัสดุ" | "设置材质" | "マテリアル設定" | "재질설정" | "تنظیم_متریال" | "عيّن_المادة" | "קבע_חומר" | "میٹریل_مقرر_کرو" =>
5588 {
5589 let key = self.arg_str(&args, 0, "");
5590 let val = self.arg_num(&args, 1, 0.0)?;
5591 let mut gfx = self.gfx.borrow_mut();
5592 let mat = gfx
5593 .material
5594 .get_or_insert_with(crate::gfx::LingMaterial::default);
5595 match key.as_str() {
5596 "albedo" => mat.albedo = val as u32,
5597 "roughness" => mat.roughness = val as f32,
5598 "metallic" => mat.metallic = val as f32,
5599 "emission" => mat.emission = val as u32,
5600 "emission_strength" => mat.emission_strength = val as f32,
5601 "specular" => mat.specular = val as f32,
5602 "specular_tint" => mat.specular_tint = val as f32,
5603 "subsurface" => mat.subsurface = val as f32,
5604 "subsurface_color" => mat.subsurface_color = val as u32,
5605 "clearcoat" => mat.clearcoat = val as f32,
5606 "clearcoat_roughness" => mat.clearcoat_roughness = val as f32,
5607 "transmission" => mat.transmission = val as f32,
5608 "ior" => mat.ior = val as f32,
5609 "iridescence" => mat.iridescence = val as f32,
5610 "sheen" => mat.sheen = val as f32,
5611 "anisotropy" => mat.anisotropy = val as f32,
5612 "anisotropy_angle" => mat.anisotropy_angle = val as f32,
5613 "toon_bands" => mat.toon_bands = val as u32,
5614 "shadow_softness" => mat.shadow_softness = val as f32,
5615 "outline_px" => mat.outline_px = val as f32,
5616 "outline_color" => mat.outline_color = val as u32,
5617 "highlight_color" => mat.highlight_color = val as u32,
5618 _ => {},
5619 }
5620 return Ok(Value::Unit);
5621 },
5622
5623 "reset_material" | "รีเซ็ตวัสดุ" | "重置材质" | "マテリアルリセット" | "재질초기화" | "بازنشانی_متریال" | "أعد_ضبط_المادة" | "אפס_חומר" | "میٹریل_ری_سیٹ" =>
5626 {
5627 self.gfx.borrow_mut().material = None;
5628 return Ok(Value::Unit);
5629 },
5630
5631 "toon_outlines"
5637 | "ตั้งเส้นขอบการ์ตูน"
5638 | "卡通轮廓"
5639 | "トゥーンアウトライン"
5640 | "툰아웃라인" | "خطوط_کارتونی" | "حدود_كرتونية" | "קווי_מתאר_מצוירים" | "ٹون_آؤٹ_لائنز" => {
5641 let px = self.arg_num(&args, 0, 0.0)? as f32;
5642 let color = self.arg_num(&args, 1, 0.0)? as u32;
5643 let thresh = self.arg_num(&args, 2, 0.05)? as f32;
5644 let mut gfx = self.gfx.borrow_mut();
5645 gfx.toon.outline_px = px;
5646 gfx.toon.outline_color = color;
5647 gfx.toon.outline_thresh = thresh;
5648 return Ok(Value::Unit);
5649 },
5650
5651 "tone_stop" | "ตั้งจุดโทน" | "色调停止" | "トーンストップ" | "톤스톱" | "نقطه_توقف_تن_رنگ" | "نقطة_توقف_اللون" | "נקודת_עצירת_גוון" | "ٹون_اسٹاپ" =>
5657 {
5658 let t = self.arg_num(&args, 0, 0.0)? as f32;
5659 let val = self.arg_num(&args, 1, 1.0)? as f32;
5660 let mut gfx = self.gfx.borrow_mut();
5661 gfx.toon.ramp.stops.push(crate::gfx::toon::ToneStop {
5662 t: t.clamp(0.0, 1.0),
5663 value: val.clamp(0.0, 1.0),
5664 });
5665 gfx.toon
5666 .ramp
5667 .stops
5668 .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
5669 return Ok(Value::Unit);
5670 },
5671
5672 "tone_smooth" | "ตั้งโทนนุ่ม" | "色调平滑" | "トーンスムーズ" | "톤스무스" | "تن_رنگ_نرم" | "تدرج_لون_ناعم" | "גוון_חלק" | "ٹون_ہموار" =>
5675 {
5676 let v = self.arg_num(&args, 0, 0.0)? as f32;
5677 self.gfx.borrow_mut().toon.ramp.smooth = v > 0.5;
5678 return Ok(Value::Unit);
5679 },
5680
5681 "tone_bezier" | "ตั้งโทนเบซิเยร์" | "色调贝塞尔" | "トーンベジェ" | "톤베지어" | "تن_رنگ_بزیه" | "تدرج_لون_بيزيه" | "גוון_בזייה" | "بیزیئر_ٹون" =>
5689 {
5690 let y1 = self.arg_num(&args, 0, 1.0 / 3.0)? as f32;
5691 let y2 = self.arg_num(&args, 1, 2.0 / 3.0)? as f32;
5692 self.gfx.borrow_mut().toon.ramp.bezier = Some([y1, y2]);
5693 return Ok(Value::Unit);
5694 },
5695
5696 "tone_bezier_off"
5698 | "ปิดโทนเบซิเยร์"
5699 | "关闭色调贝塞尔"
5700 | "トーンベジェオフ"
5701 | "톤베지어끄기" | "خاموشکردن_بزیه" | "إيقاف_تدرج_بيزيه" | "כבה_גוון_בזייה" | "بیزیئر_ٹون_بند" => {
5702 self.gfx.borrow_mut().toon.ramp.bezier = None;
5703 return Ok(Value::Unit);
5704 },
5705
5706 "tone_ramp_reset"
5708 | "รีเซ็ตการไล่โทน"
5709 | "重置色调渐变"
5710 | "トーンランプリセット"
5711 | "톤램프리셋" | "بازنشانی_شیب_تن_رنگ" | "أعد_ضبط_تدرج_اللون" | "אפס_שיפוע_גוון" | "ٹون_ریمپ_ری_سیٹ" => {
5712 self.gfx.borrow_mut().toon.ramp = crate::gfx::toon::ToneRamp::default();
5713 return Ok(Value::Unit);
5714 },
5715
5716 "tone_ramp_clear"
5718 | "ล้างการไล่โทน"
5719 | "清除色调渐变"
5720 | "トーンランプクリア"
5721 | "톤램프클리어" | "پاککردن_شیب_تن_رنگ" | "امسح_تدرج_اللون" | "נקה_שיפוע_גוון" | "ٹون_ریمپ_صاف" => {
5722 self.gfx.borrow_mut().toon.ramp.stops.clear();
5723 return Ok(Value::Unit);
5724 },
5725
5726 "tone_soft" | "โทนขอบนุ่ม" | "色调柔边" | "トーンソフト" | "톤소프트" | "تن_رنگ_لبهنرم" | "تدرج_ناعم_الحواف" | "גוון_קצה_רך" | "نرم_کنارہ_ٹون" | "tonalité_douce" | "weicher_ton" | "мягкий_тон" => {
5734 let s = self.arg_num(&args, 0, 0.32)? as f32;
5735 let sh = self.arg_num(&args, 1, 0.65)? as f32;
5736 let mut gfx = self.gfx.borrow_mut();
5737 gfx.toon.ramp.soft = s.clamp(0.0, 1.0);
5738 gfx.toon.ramp.sheen = sh.clamp(0.0, 1.0);
5739 return Ok(Value::Unit);
5740 },
5741
5742 "set_ssao" | "ตั้งเงาสัมผัส" | "环境光遮蔽" | "アンビエントオクルージョン"
5747 | "앰비언트오클루전" | "تنظیم_انسداد_محیطی" | "عيّن_تظليل_محيطي" | "קבע_הצללה_סביבתית" | "ایس_ایس_اے_او_مقرر_کرو" | "définir_ssao" | "ssao_setzen" | "задать_ssao" => {
5748 let s = self.arg_num(&args, 0, 0.35)? as f32;
5749 let r = self.arg_num(&args, 1, 6.0)? as f32;
5750 let z = self.arg_num(&args, 2, 12.0)? as f32;
5751 let mut gfx = self.gfx.borrow_mut();
5752 gfx.toon.ao_strength = s.clamp(0.0, 1.0);
5753 gfx.toon.ao_radius = r.max(1.0);
5754 gfx.toon.ao_range = z.max(0.01);
5755 return Ok(Value::Unit);
5756 },
5757
5758 "set_fxaa" | "ลบรอยหยัก" | "屏幕抗锯齿" | "画面アンチエイリアス" | "화면안티앨리어싱" | "تنظیم_ضدلبهدندانه_سریع" | "عيّن_مضاد_التسنن_السريع" | "קבע_החלקת_מסך" | "ایف_ایکس_اے_اے_مقرر_کرو" | "définir_fxaa" | "fxaa_setzen" | "задать_fxaa" => {
5764 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
5765 self.gfx.borrow_mut().toon.fxaa = on;
5766 return Ok(Value::Unit);
5767 },
5768
5769 "set_bloom" | "ตั้งบลูม" | "泛光" | "ブルーム" | "블룸" | "تنظیم_درخشش" | "عيّن_التوهج" | "קבע_זוהר" | "بلوم_مقرر_کرو" | "définir_bloom" | "bloom_setzen" | "задать_блум" => {
5774 let s = self.arg_num(&args, 0, 0.45)? as f32;
5775 let t = self.arg_num(&args, 1, 0.74)? as f32;
5776 let mut gfx = self.gfx.borrow_mut();
5777 gfx.toon.bloom_strength = s.max(0.0);
5778 gfx.toon.bloom_thresh = t.clamp(0.0, 0.99);
5779 return Ok(Value::Unit);
5780 },
5781
5782 "shadow_smooth" | "ตั้งเงานุ่ม" | "柔化阴影" | "影ソフト" | "그림자부드럽게" | "سایه_نرم" | "ظل_ناعم" | "צל_חלק" | "نرم_سایہ" =>
5785 {
5786 let s = self.arg_num(&args, 0, 0.0)? as f32;
5787 let mut gfx = self.gfx.borrow_mut();
5788 gfx.toon.ramp.smooth = s > 0.05;
5789 if s > 0.05 {
5790 let y1 = (0.333 + s * 0.2).clamp(0.0, 1.0);
5791 let y2 = (0.667 - s * 0.2).clamp(0.0, 1.0);
5792 gfx.toon.ramp.bezier = Some([y1, y2]);
5793 } else {
5794 gfx.toon.ramp.bezier = None;
5795 }
5796 return Ok(Value::Unit);
5797 },
5798
5799 "toon_highlight"
5801 | "ตั้งไฮไลท์การ์ตูน"
5802 | "卡通高光"
5803 | "トゥーンハイライト"
5804 | "툰하이라이트" | "هایلایت_کارتونی" | "إبراز_كرتوني" | "הדגשה_מצוירת" | "ٹون_ہائی_لائٹ" => {
5805 let _strength = self.arg_num(&args, 0, 0.0)? as f32;
5807 let _thresh = self.arg_num(&args, 2, 0.78)? as f32;
5808 return Ok(Value::Unit);
5810 },
5811
5812 "set_ambient" | "ตั้งแสงรอบข้าง" | "环境光" | "環境光設定" | "환경광설정" | "تنظیم_نور_محیطی" | "عيّن_الإضاءة_المحيطة" | "קבע_תאורה_סביבתית" | "ماحولیاتی_روشنی_مقرر_کرو" | "définir_ambiante" | "umgebungslicht_setzen" | "задать_фон" =>
5814 {
5815 let v = self.arg_num(&args, 0, 0.15)? as f32;
5816 self.gfx.borrow_mut().ambient = v;
5817 return Ok(Value::Unit);
5818 },
5819
5820 "set_fog" | "ตั้งหมอก" | "雾" | "霧設定" | "안개설정" | "تنظیم_مه" | "عيّن_الضباب" | "קבע_ערפל" | "دھند_مقرر_کرو" => {
5823 let r = self.arg_num(&args, 0, 0.0)?.clamp(0.0, 255.0) as u32;
5824 let g = self.arg_num(&args, 1, 0.0)?.clamp(0.0, 255.0) as u32;
5825 let b = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 255.0) as u32;
5826 let start = self.arg_num(&args, 3, 0.0)? as f32;
5827 let end = self.arg_num(&args, 4, 0.0)? as f32;
5828 let mut gfx = self.gfx.borrow_mut();
5829 gfx.fog_color = (r << 16) | (g << 8) | b;
5830 gfx.fog_start = start;
5831 gfx.fog_end = end;
5832 return Ok(Value::Unit);
5833 },
5834
5835 "วาดสามเหลี่ยม3มิติ" | "draw_triangle_3d" | "triangle3d" | "رسم_مثلث_سهبعدی" | "ارسم_مثلثا_ثلاثي_الأبعاد" | "צייר_משולש_תלת_ממדי" | "تھری_ڈی_مثلث_کھینچو" =>
5839 {
5840 let ax = self.arg_num(&args, 0, 0.0)? as f32;
5841 let ay = self.arg_num(&args, 1, 0.0)? as f32;
5842 let az = self.arg_num(&args, 2, 0.0)? as f32;
5843 let bx = self.arg_num(&args, 3, 0.0)? as f32;
5844 let by = self.arg_num(&args, 4, 0.0)? as f32;
5845 let bz = self.arg_num(&args, 5, 0.0)? as f32;
5846 let cx = self.arg_num(&args, 6, 0.0)? as f32;
5847 let cy = self.arg_num(&args, 7, 0.0)? as f32;
5848 let cz = self.arg_num(&args, 8, 0.0)? as f32;
5849
5850 let mut gfx = self.gfx.borrow_mut();
5851
5852 if gfx.mesh_capture.is_some() {
5854 let col = gfx.color;
5855 gfx.mesh_capture
5856 .as_mut()
5857 .unwrap()
5858 .push(([ax, ay, az, bx, by, bz, cx, cy, cz], col));
5859 return Ok(Value::Unit);
5860 }
5861
5862 gfx.submit_triangle(ax, ay, az, bx, by, bz, cx, cy, cz);
5863 return Ok(Value::Unit);
5864 },
5865
5866 "เริ่มอบเมช" | "mesh_bake_begin" | "شروع_پخت_مش" | "ابدأ_خبز_الشبكة" | "התחל_אפיית_רשת" | "میش_بیک_شروع" => {
5868 self.gfx.borrow_mut().mesh_capture = Some(Vec::new());
5869 return Ok(Value::Unit);
5870 },
5871
5872 "เมชแคชรับ" | "mesh_cache_get" | "دریافت_کش_مش" | "اجلب_مخبأ_الشبكة" | "קבל_מטמון_רשת" | "میش_کیش_حاصل_کرو" => {
5874 let key = self.arg_num(&args, 0, 0.0)? as i64;
5875 let h = self.gfx.borrow().mesh_cache.get(&key).copied();
5876 return Ok(Value::Number(h.map(|x| x as f64).unwrap_or(-1.0)));
5877 },
5878
5879 "เมชแคชตั้ง" | "mesh_cache_put" | "ذخیره_در_کش_مش" | "ضع_في_مخبأ_الشبكة" | "שמור_במטמון_רשת" | "میش_کیش_رکھو" => {
5881 let key = self.arg_num(&args, 0, 0.0)? as i64;
5882 let h = self.arg_num(&args, 1, 0.0)? as usize;
5883 let mut gfx = self.gfx.borrow_mut();
5884 const CAP: usize = 256;
5885 if gfx.mesh_cache.len() >= CAP {
5886 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5887 gfx.mesh_cache.clear();
5888 for id in evict {
5889 if id < gfx.meshes.len() {
5890 gfx.meshes[id].clear();
5891 gfx.mesh_free.push(id);
5892 }
5893 }
5894 }
5895 gfx.mesh_cache.insert(key, h);
5896 return Ok(Value::Unit);
5897 },
5898
5899 "เมชแคชล้าง" | "mesh_cache_clear" | "پاککردن_کش_مش" | "امسح_مخبأ_الشبكة" | "נקה_מטמון_רשת" | "میش_کیش_صاف" => {
5901 let mut gfx = self.gfx.borrow_mut();
5902 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5903 gfx.mesh_cache.clear();
5904 for id in evict {
5905 if id < gfx.meshes.len() {
5906 gfx.meshes[id].clear();
5907 gfx.mesh_free.push(id);
5908 }
5909 }
5910 return Ok(Value::Unit);
5911 },
5912
5913 "จบอบเมช" | "mesh_bake_end" | "پایان_پخت_مش" | "أنهِ_خبز_الشبكة" | "סיים_אפיית_רשת" | "میش_بیک_ختم" => {
5915 let mut gfx = self.gfx.borrow_mut();
5916 let tris = gfx.mesh_capture.take().unwrap_or_default();
5917 let id = gfx.mesh_register(tris);
5918 return Ok(Value::Number(id as f64));
5919 },
5920
5921 "วาดอบเมช" | "mesh_bake_draw" | "วาดอบเมชสี" | "mesh_bake_draw_col" | "رسم_مش_پخته" | "ارسم_شبكة_مخبوزة" | "צייר_רשת_אפויה" | "بیکڈ_میش_کھینچو" =>
5925 {
5926 let baked_col = matches!(name, "วาดอบเมชสี" | "mesh_bake_draw_col");
5927 let id = self.arg_num(&args, 0, 0.0)? as usize;
5928 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5929 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5930 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5931 let rx = self.arg_num(&args, 4, 1.0)? as f32;
5932 let ry = self.arg_num(&args, 5, 0.0)? as f32;
5933 let rz = self.arg_num(&args, 6, 0.0)? as f32;
5934 let ux = self.arg_num(&args, 7, 0.0)? as f32;
5935 let uy = self.arg_num(&args, 8, 1.0)? as f32;
5936 let uz = self.arg_num(&args, 9, 0.0)? as f32;
5937 let s = self.arg_num(&args, 10, 1.0)? as f32;
5938 self.gfx
5939 .borrow_mut()
5940 .mesh_draw(id, ox, oy, oz, rx, ry, rz, ux, uy, uz, s, baked_col);
5941 return Ok(Value::Unit);
5942 },
5943
5944 "draw_quad_3d"
5948 | "quad3d"
5949 | "วาดสี่เหลี่ยม3มิติ"
5950 | "draw_pent_3d"
5951 | "pent3d"
5952 | "วาดห้าเหลี่ยม3มิติ"
5953 | "draw_hex_3d"
5954 | "hex3d"
5955 | "วาดหกเหลี่ยม3มิติ"
5956 | "draw_polygon_3d"
5957 | "polygon3d"
5958 | "วาดรูปหลายเหลี่ยม3มิติ" | "رسم_چهارضلعی_سهبعدی" | "ارسم_رباعيا_ثلاثي_الأبعاد" | "צייר_מרובע_תלת_ממדי" | "تھری_ڈی_چوکور_کھینچو" => {
5959 let mut wxs: [f32; 8] = [0.0; 8];
5961 let mut wys: [f32; 8] = [0.0; 8];
5962 let mut wzs: [f32; 8] = [0.0; 8];
5963 let n_verts;
5964
5965 if args.len() == 1 {
5966 let list = match &args[0] {
5968 Value::List(l) => l.clone(),
5969 _ => {
5970 return Err(EvalErr::from("draw_polygon_3d: expected list".to_string()))
5971 },
5972 };
5973 let coords: Vec<f32> = list
5974 .iter()
5975 .map(|v| match v {
5976 Value::Number(n) => *n as f32,
5977 _ => 0.0,
5978 })
5979 .collect();
5980 n_verts = (coords.len() / 3).min(8);
5981 for i in 0..n_verts {
5982 wxs[i] = coords[i * 3];
5983 wys[i] = coords[i * 3 + 1];
5984 wzs[i] = coords[i * 3 + 2];
5985 }
5986 } else {
5987 n_verts = (args.len() / 3).min(8);
5989 for i in 0..n_verts {
5990 wxs[i] = self.arg_num(&args, i * 3, 0.0)? as f32;
5991 wys[i] = self.arg_num(&args, i * 3 + 1, 0.0)? as f32;
5992 wzs[i] = self.arg_num(&args, i * 3 + 2, 0.0)? as f32;
5993 }
5994 }
5995 if n_verts < 3 {
5996 return Ok(Value::Unit);
5997 }
5998
5999 let mut gfx = self.gfx.borrow_mut();
6000
6001 if gfx.mesh_capture.is_some() {
6007 let col = gfx.color;
6008 let cap = gfx.mesh_capture.as_mut().unwrap();
6009 for i in 1..n_verts - 1 {
6010 cap.push((
6011 [
6012 wxs[0], wys[0], wzs[0],
6013 wxs[i], wys[i], wzs[i],
6014 wxs[i + 1], wys[i + 1], wzs[i + 1],
6015 ],
6016 col,
6017 ));
6018 }
6019 return Ok(Value::Unit);
6020 }
6021
6022 let normal = crate::gfx::poly::face_normal(
6024 wxs[0], wys[0], wzs[0], wxs[1], wys[1], wzs[1], wxs[2], wys[2], wzs[2],
6025 );
6026
6027 let mut wcs: [u32; 8] = [0; 8];
6029 if gfx.flat_shade {
6030 let c = gfx.color;
6031 for wc in wcs.iter_mut().take(n_verts) {
6032 *wc = c;
6033 }
6034 } else if let Some(ref mat) = gfx.material.clone() {
6035 let cam = [gfx.camera.cx, gfx.camera.cy, gfx.camera.zdist];
6036 let lights: Vec<_> = gfx.lights.clone();
6037 let ambient = gfx.ambient;
6038 for i in 0..n_verts {
6039 let v = [wxs[i], wys[i], wzs[i]];
6040 let vd = [cam[0] - v[0], cam[1] - v[1], cam[2] - v[2]];
6041 wcs[i] = crate::gfx::material::shade(mat, normal, vd, v, &lights, ambient);
6042 }
6043 } else {
6044 let base = gfx.color;
6045 let lights: Vec<_> = gfx.lights.clone();
6046 let ambient = gfx.ambient;
6047 for i in 0..n_verts {
6048 wcs[i] = crate::gfx::light::compute_lit_color_linear(
6049 base,
6050 normal,
6051 [wxs[i], wys[i], wzs[i]],
6052 &lights,
6053 ambient,
6054 );
6055 }
6056 }
6057
6058 let near = -gfx.camera.zdist + 0.05;
6060 let mut clip_in: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6061 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6062 for i in 0..n_verts {
6063 let d = gfx.camera.depth(wxs[i], wys[i], wzs[i]);
6064 clip_in[i] = (wxs[i], wys[i], wzs[i], d, wcs[i]);
6065 }
6066 let mut clip_out: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6067 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6068 let pn = crate::gfx::poly::clip_near(&clip_in, n_verts, near, &mut clip_out);
6069 if pn < 3 {
6070 return Ok(Value::Unit);
6071 }
6072
6073 let mut proj: [(f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6075 [(0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6076 for i in 0..pn {
6077 let (sx, sy, sz) =
6078 gfx.camera
6079 .project(clip_out[i].0, clip_out[i].1, clip_out[i].2);
6080 let fc = gfx.fog_apply(clip_out[i].4, sz);
6081 proj[i] = (sx, sy, sz, fc);
6082 }
6083
6084 let unlit = gfx.flat_shade;
6086 crate::gfx::poly::fan_emit_proj(
6087 &proj,
6088 pn,
6089 |x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2| {
6090 gfx.depth_queue.push_triangle_g_zv(
6091 x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2, 3, unlit,
6092 );
6093 },
6094 );
6095 return Ok(Value::Unit);
6096 },
6097
6098 "วาดเส้น3มิติ" | "draw_line_3d" | "line3d" | "画3D线" | "3D線描く" | "3D선그리기" | "رسم_خط_سهبعدی" | "ارسم_خطا_ثلاثي_الأبعاد" | "צייר_קו_תלת_ממדי" | "تھری_ڈی_لکیر_کھینچو" =>
6102 {
6103 let ax = self.arg_num(&args, 0, 0.0)? as f32;
6104 let ay = self.arg_num(&args, 1, 0.0)? as f32;
6105 let az = self.arg_num(&args, 2, 0.0)? as f32;
6106 let bx = self.arg_num(&args, 3, 0.0)? as f32;
6107 let by = self.arg_num(&args, 4, 0.0)? as f32;
6108 let bz = self.arg_num(&args, 5, 0.0)? as f32;
6109
6110 let mut gfx = self.gfx.borrow_mut();
6111 let color = gfx.color;
6112 let near = -gfx.camera.zdist + 0.05;
6114 let mut lax = ax;
6115 let mut lay = ay;
6116 let mut laz = az;
6117 let mut lbx = bx;
6118 let mut lby = by;
6119 let mut lbz = bz;
6120 let da_raw = gfx.camera.depth(lax, lay, laz);
6121 let db_raw = gfx.camera.depth(lbx, lby, lbz);
6122 if da_raw <= near && db_raw <= near {
6123 return Ok(Value::Unit);
6124 }
6125 if da_raw <= near {
6126 let t = (near - da_raw) / (db_raw - da_raw);
6127 lax += t * (lbx - lax);
6128 lay += t * (lby - lay);
6129 laz += t * (lbz - laz);
6130 } else if db_raw <= near {
6131 let t = (near - da_raw) / (db_raw - da_raw);
6132 lbx = lax + t * (lbx - lax);
6133 lby = lay + t * (lby - lay);
6134 lbz = laz + t * (lbz - laz);
6135 }
6136 if !gfx.edge_set.try_insert(lax, lay, laz, lbx, lby, lbz) {
6138 return Ok(Value::Unit);
6139 }
6140 let (sax, say, da) = gfx.camera.project(lax, lay, laz);
6141 let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
6142 let depth = (da + db) / 2.0;
6143 let color = gfx.fog_apply(color, depth);
6144 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
6145 return Ok(Value::Unit);
6146 },
6147
6148 #[cfg(not(target_arch = "wasm32"))]
6158 "orb_shell" | "球壳" | "オーブ殻" | "오브껍질" | "เปลือกทรงกลม" | "پوسته_کروی" | "قشرة_كروية" | "קליפת_כדור" | "کروی_خول" | "coque_orbe" | "orb_hülle" | "оболочка_сферы" =>
6159 {
6160 let cx = self.arg_num(&args, 0, 0.)? as f32;
6161 let cy = self.arg_num(&args, 1, 0.)? as f32;
6162 let cz = self.arg_num(&args, 2, 0.)? as f32;
6163 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6164 let ry = self.arg_num(&args, 4, 0.)? as f32;
6165 let rx = self.arg_num(&args, 5, 0.)? as f32;
6166 let density = (self.arg_num(&args, 6, 10.)? as i32).clamp(1, 48);
6167 let tr = (self.arg_num(&args, 7, 230.)? as f32).clamp(0., 255.);
6168 let tg = (self.arg_num(&args, 8, 230.)? as f32).clamp(0., 255.);
6169 let tb = (self.arg_num(&args, 9, 235.)? as f32).clamp(0., 255.);
6170 let (cyr, syr) = (ry.cos(), ry.sin());
6171 let (cxr, sxr) = (rx.cos(), rx.sin());
6172 let tau = std::f32::consts::TAU;
6173 let pi = std::f32::consts::PI;
6174 let turns = 6.0_f32; let nseg = 96; let inv_r = if radius.abs() > 1e-5 {
6177 1.0 / radius
6178 } else {
6179 0.0
6180 };
6181 let pt = |u: f32, theta0: f32, dir: f32| -> ([f32; 3], f32) {
6184 let phi = pi * u; let th = dir * turns * tau * u + theta0;
6186 let (mut x, y, mut z) = (
6187 phi.sin() * th.cos() * radius,
6188 phi.cos() * radius,
6189 phi.sin() * th.sin() * radius,
6190 );
6191 let x1 = x * cyr + z * syr; let z1 = -x * syr + z * cyr;
6193 x = x1;
6194 z = z1;
6195 let y2 = y * cxr - z * sxr; let z2 = y * sxr + z * cxr;
6197 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.0, 1.0);
6199 ([cx + x, cy + y2, cz + z2], facing)
6200 };
6201 let mut gfx = self.gfx.borrow_mut();
6202 let near = -gfx.camera.zdist + 0.05;
6203 let seg = |gfx: &mut crate::gfx::GfxState, a: [f32; 3], b: [f32; 3], lum: f32| {
6205 let (mut lax, mut lay, mut laz) = (a[0], a[1], a[2]);
6206 let (mut lbx, mut lby, mut lbz) = (b[0], b[1], b[2]);
6207 let da = gfx.camera.depth(lax, lay, laz);
6208 let db = gfx.camera.depth(lbx, lby, lbz);
6209 if da <= near && db <= near {
6210 return;
6211 }
6212 if da <= near {
6213 let t = (near - da) / (db - da);
6214 lax += t * (lbx - lax);
6215 lay += t * (lby - lay);
6216 laz += t * (lbz - laz);
6217 } else if db <= near {
6218 let t = (near - da) / (db - da);
6219 lbx = lax + t * (lbx - lax);
6220 lby = lay + t * (lby - lay);
6221 lbz = laz + t * (lbz - laz);
6222 }
6223 let (sax, say, da2) = gfx.camera.project(lax, lay, laz);
6224 let (sbx, sby, db2) = gfx.camera.project(lbx, lby, lbz);
6225 let l = (0.12 + 0.88 * lum).clamp(0.0, 1.0);
6227 let cr = (tr * l) as u32;
6228 let cg = (tg * l) as u32;
6229 let cb = (tb * l) as u32;
6230 let color = (cr << 16) | (cg << 8) | cb;
6231 gfx.depth_queue
6232 .push_line((da2 + db2) * 0.5, color, sax, say, sbx, sby);
6233 };
6234 for &dir in &[1.0_f32, -1.0_f32] {
6236 for s in 0..density {
6237 let theta0 = s as f32 * tau / density as f32;
6238 let mut prev = pt(0.0, theta0, dir);
6239 for k in 1..=nseg {
6240 let cur = pt(k as f32 / nseg as f32, theta0, dir);
6241 seg(&mut gfx, prev.0, cur.0, (prev.1 + cur.1) * 0.5);
6242 prev = cur;
6243 }
6244 }
6245 }
6246 return Ok(Value::Unit);
6247 },
6248
6249 #[cfg(not(target_arch = "wasm32"))]
6257 "orb_particles" | "球内粒子" | "オーブ粒子" | "오브입자" | "อนุภาคทรงกลม" | "ذرات_کروی" | "جسيمات_كروية" | "חלקיקי_כדור" | "کروی_ذرات" | "particules_orbe" | "orb_partikel" | "частицы_сферы" =>
6258 {
6259 let cx = self.arg_num(&args, 0, 0.)? as f32;
6260 let cy = self.arg_num(&args, 1, 0.)? as f32;
6261 let cz = self.arg_num(&args, 2, 0.)? as f32;
6262 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6263 let count = (self.arg_num(&args, 4, 160.)? as i32).clamp(1, 4000);
6264 let t = self.arg_num(&args, 5, 0.)? as f32;
6265 let tr = (self.arg_num(&args, 6, 255.)? as f32).clamp(0., 255.);
6266 let tg = (self.arg_num(&args, 7, 255.)? as f32).clamp(0., 255.);
6267 let tb = (self.arg_num(&args, 8, 255.)? as f32).clamp(0., 255.);
6268 let inv_r = if radius.abs() > 1e-5 {
6269 1.0 / radius
6270 } else {
6271 0.0
6272 };
6273 let h = |mut x: u32| -> f32 {
6275 x = x.wrapping_mul(747796405).wrapping_add(2891336453);
6276 x = ((x >> ((x >> 28).wrapping_add(4))) ^ x).wrapping_mul(277803737);
6277 (((x >> 22) ^ x) & 0xFFFFFF) as f32 / 16_777_216.0
6278 };
6279 let tau = std::f32::consts::TAU;
6280 let (cyr, syr) = ((t * 0.5).cos(), (t * 0.5).sin());
6282 let (cxr, sxr) = ((t * 0.23).cos(), (t * 0.23).sin());
6283 let mut gfx = self.gfx.borrow_mut();
6284 let near = -gfx.camera.zdist + 0.05;
6285 let (sw, sh) = (gfx.width as i32, gfx.height as i32);
6286 for i in 0..count {
6287 let i = i as u32;
6288 let u = h(i.wrapping_mul(3) + 1);
6290 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) = (
6294 rr * ph.sin() * th.cos(),
6295 rr * ph.cos(),
6296 rr * ph.sin() * th.sin(),
6297 );
6298 let x1 = x * cyr + z * syr;
6300 let z1 = -x * syr + z * cyr;
6301 x = x1;
6302 z = z1;
6303 let y2 = y * cxr - z * sxr;
6304 let z2 = y * sxr + z * cxr;
6305 let (wx, wy, wz) = (cx + x, cy + y2, cz + z2);
6306 if gfx.camera.depth(wx, wy, wz) <= near {
6307 continue;
6308 }
6309 let (sx, sy, dep) = gfx.camera.project(wx, wy, wz);
6310 let sxi = sx as i32;
6311 let syi = sy as i32;
6312 if sxi < 0 || syi < 0 || sxi >= sw || syi >= sh {
6313 continue;
6314 }
6315 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.15, 1.0);
6317 let l = facing;
6318 let cr = (tr * l) as u32;
6319 let cg = (tg * l) as u32;
6320 let cb = (tb * l) as u32;
6321 let color = (cr << 16) | (cg << 8) | cb;
6322 let len = if facing > 0.7 { 1.0 } else { 0.0 };
6324 gfx.depth_queue.push_line(dep, color, sx, sy, sx + len, sy);
6325 }
6326 return Ok(Value::Unit);
6327 },
6328
6329 "project_3d" | "投影3D" | "3D投影" | "3D투영" | "ฉาย3มิติ" | "فرافکنی_سهبعدی" | "إسقاط_ثلاثي_الأبعاد" | "הטלה_תלת_ממדית" | "تھری_ڈی_پروجیکشن" | "projeter_3d" | "projizieren_3d" | "проекция_3d" => {
6333 let x = self.arg_num(&args, 0, 0.0)? as f32;
6334 let y = self.arg_num(&args, 1, 0.0)? as f32;
6335 let z = self.arg_num(&args, 2, 0.0)? as f32;
6336 let gfx = self.gfx.borrow();
6337 let near = -gfx.camera.zdist + 0.05;
6338 let d = gfx.camera.depth(x, y, z);
6339 if d <= near {
6340 return Ok(Value::List(Rc::new(vec![
6341 Value::Number(-99999.0),
6342 Value::Number(-99999.0),
6343 Value::Number(d as f64),
6344 ])));
6345 }
6346 let (sx, sy, depth) = gfx.camera.project(x, y, z);
6347 return Ok(Value::List(Rc::new(vec![
6348 Value::Number(sx as f64),
6349 Value::Number(sy as f64),
6350 Value::Number(depth as f64),
6351 ])));
6352 },
6353
6354 #[cfg(not(target_arch = "wasm32"))]
6361 "mouse_ray" => {
6362 let gfx = self.gfx.borrow();
6363 let (mx, my) = gfx
6364 .window
6365 .as_ref()
6366 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
6367 .unwrap_or((gfx.camera.cx, gfx.camera.cy));
6368 let cam = &gfx.camera;
6369 let dcx = (mx - cam.cx) / cam.focal;
6372 let dcy = (my - cam.cy) / cam.focal;
6373 let a_x = dcx;
6376 let a_y = cam.crx * dcy + cam.srx * 1.0;
6377 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6378 let dir_x = cam.cry * a_x + cam.sry * a_z;
6379 let dir_y = a_y;
6380 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6381 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6382 .sqrt()
6383 .max(1e-6);
6384 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6385 let ox = cam.tx;
6397 let oy = cam.ty;
6398 let oz = cam.tz;
6399 return Ok(Value::List(Rc::new(vec![
6400 Value::Number(ox as f64),
6401 Value::Number(oy as f64),
6402 Value::Number(oz as f64),
6403 Value::Number(dir_x as f64),
6404 Value::Number(dir_y as f64),
6405 Value::Number(dir_z as f64),
6406 ])));
6407 },
6408 #[cfg(target_arch = "wasm32")]
6409 "mouse_ray" => {
6410 let gfx = self.gfx.borrow();
6411 let mx = crate::gfx::wasm_mouse_x();
6412 let my = crate::gfx::wasm_mouse_y();
6413 let cam = &gfx.camera;
6414 let dcx = (mx - cam.cx) / cam.focal;
6415 let dcy = (my - cam.cy) / cam.focal;
6416 let a_x = dcx;
6417 let a_y = cam.crx * dcy + cam.srx * 1.0;
6418 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6419 let dir_x = cam.cry * a_x + cam.sry * a_z;
6420 let dir_y = a_y;
6421 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6422 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6423 .sqrt()
6424 .max(1e-6);
6425 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6426 let ox = cam.tx;
6427 let oy = cam.ty;
6428 let oz = cam.tz;
6429 return Ok(Value::List(Rc::new(vec![
6430 Value::Number(ox as f64),
6431 Value::Number(oy as f64),
6432 Value::Number(oz as f64),
6433 Value::Number(dir_x as f64),
6434 Value::Number(dir_y as f64),
6435 Value::Number(dir_z as f64),
6436 ])));
6437 },
6438 #[cfg(not(target_arch = "wasm32"))]
6441 "draw_poly" | "填充多边形" | "ポリゴン塗り" | "다각형채우기" | "เติมรูปหลายเหลี่ยม" | "رسم_چندضلعی" | "ارسم_مضلع" | "צייר_מצולע" | "کثیر_الاضلاع_کھینچو" | "dessiner_polygone" | "polygon_zeichnen" | "рисовать_полигон" =>
6442 {
6443 let mut pts: Vec<[f32; 2]> = Vec::new();
6444 if let Some(Value::List(v)) = args.first() {
6445 let mut i = 0;
6446 while i + 1 < v.len() {
6447 let x = self.to_number(&v[i]).unwrap_or(0.0) as f32;
6448 let y = self.to_number(&v[i + 1]).unwrap_or(0.0) as f32;
6449 pts.push([x, y]);
6450 i += 2;
6451 }
6452 }
6453 if pts.len() >= 3 {
6454 if pts[0] != pts[pts.len() - 1] {
6455 let p0 = pts[0];
6456 pts.push(p0);
6457 } let mut gfx = self.gfx.borrow_mut();
6459 let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
6460 crate::gfx::raster::fill_contours_aa(
6461 &mut gfx.buffer,
6462 w,
6463 h,
6464 color,
6465 add,
6466 std::slice::from_ref(&pts),
6467 );
6468 }
6469 return Ok(Value::Unit);
6470 },
6471
6472 "vtex_grid" | "ลายตาราง" | "纹格" | "格子模様" | "격자무늬" | "الگوی_شبکه" | "نقش_شبكة" | "דוגמת_רשת" | "نقش_جالی" | "motif_grille" | "muster_gitter" | "узор_сетка" =>
6481 {
6482 let cx = self.arg_num(&args, 0, 0.)? as f32;
6483 let cy = self.arg_num(&args, 1, 0.)? as f32;
6484 let cz = self.arg_num(&args, 2, 0.)? as f32;
6485 let ux = self.arg_num(&args, 3, 1.)? as f32;
6486 let uy = self.arg_num(&args, 4, 0.)? as f32;
6487 let uz = self.arg_num(&args, 5, 0.)? as f32;
6488 let vx = self.arg_num(&args, 6, 0.)? as f32;
6489 let vy = self.arg_num(&args, 7, 0.)? as f32;
6490 let vz = self.arg_num(&args, 8, 1.)? as f32;
6491 let cols = self.arg_num(&args, 9, 10.)? as usize;
6492 let rows = self.arg_num(&args, 10, 10.)? as usize;
6493 let cw = self.arg_num(&args, 11, 1.)? as f32;
6494 let ch = self.arg_num(&args, 12, 1.)? as f32;
6495 let fr = self.arg_num(&args, 13, 0.)? as f32;
6496 let hue = self.arg_num(&args, 14, 0.)? as f32;
6497 let mut gfx = self.gfx.borrow_mut();
6498 let cam = gfx.camera.clone();
6499 crate::gfx::vtex::draw_grid(
6500 &mut gfx.depth_queue,
6501 &cam,
6502 cx,
6503 cy,
6504 cz,
6505 ux,
6506 uy,
6507 uz,
6508 vx,
6509 vy,
6510 vz,
6511 cols,
6512 rows,
6513 cw,
6514 ch,
6515 fr,
6516 hue,
6517 );
6518 return Ok(Value::Unit);
6519 },
6520
6521 "vtex_rings" | "ลายวงซ้อน" | "纹环" | "同心円" | "동심원" | "الگوی_حلقه" | "نقش_حلقات" | "דוגמת_טבעות" | "نقش_حلقے" | "motif_anneaux" | "muster_ringe" | "узор_кольца" => {
6523 let cx = self.arg_num(&args, 0, 0.)? as f32;
6524 let cy = self.arg_num(&args, 1, 0.)? as f32;
6525 let cz = self.arg_num(&args, 2, 0.)? as f32;
6526 let ux = self.arg_num(&args, 3, 1.)? as f32;
6527 let uy = self.arg_num(&args, 4, 0.)? as f32;
6528 let uz = self.arg_num(&args, 5, 0.)? as f32;
6529 let vx = self.arg_num(&args, 6, 0.)? as f32;
6530 let vy = self.arg_num(&args, 7, 0.)? as f32;
6531 let vz = self.arg_num(&args, 8, 1.)? as f32;
6532 let nr = self.arg_num(&args, 9, 6.)? as usize;
6533 let ns = self.arg_num(&args, 10, 6.)? as usize;
6534 let mr = self.arg_num(&args, 11, 3.)? as f32;
6535 let tw = self.arg_num(&args, 12, 0.)? as f32;
6536 let fr = self.arg_num(&args, 13, 0.)? as f32;
6537 let hue = self.arg_num(&args, 14, 0.)? as f32;
6538 let mut gfx = self.gfx.borrow_mut();
6539 let cam = gfx.camera.clone();
6540 crate::gfx::vtex::draw_rings(
6541 &mut gfx.depth_queue,
6542 &cam,
6543 cx,
6544 cy,
6545 cz,
6546 ux,
6547 uy,
6548 uz,
6549 vx,
6550 vy,
6551 vz,
6552 nr,
6553 ns,
6554 mr,
6555 tw,
6556 fr,
6557 hue,
6558 );
6559 return Ok(Value::Unit);
6560 },
6561
6562 "vtex_star" | "ลายดาว" | "纹星" | "星模様" | "별무늬" | "الگوی_ستاره" | "نقش_نجمة" | "דוגמת_כוכב" | "نقش_ستارہ" | "motif_étoile" | "muster_stern" | "узор_звезда" => {
6564 let cx = self.arg_num(&args, 0, 0.)? as f32;
6565 let cy = self.arg_num(&args, 1, 0.)? as f32;
6566 let cz = self.arg_num(&args, 2, 0.)? as f32;
6567 let ux = self.arg_num(&args, 3, 1.)? as f32;
6568 let uy = self.arg_num(&args, 4, 0.)? as f32;
6569 let uz = self.arg_num(&args, 5, 0.)? as f32;
6570 let vx = self.arg_num(&args, 6, 0.)? as f32;
6571 let vy = self.arg_num(&args, 7, 0.)? as f32;
6572 let vz = self.arg_num(&args, 8, 1.)? as f32;
6573 let np = self.arg_num(&args, 9, 6.)? as usize;
6574 let ro = self.arg_num(&args, 10, 2.)? as f32;
6575 let ri = self.arg_num(&args, 11, 1.)? as f32;
6576 let rs = self.arg_num(&args, 12, 0.01)? as f32;
6577 let fr = self.arg_num(&args, 13, 0.)? as f32;
6578 let hue = self.arg_num(&args, 14, 0.)? as f32;
6579 let mut gfx = self.gfx.borrow_mut();
6580 let cam = gfx.camera.clone();
6581 crate::gfx::vtex::draw_star(
6582 &mut gfx.depth_queue,
6583 &cam,
6584 cx,
6585 cy,
6586 cz,
6587 ux,
6588 uy,
6589 uz,
6590 vx,
6591 vy,
6592 vz,
6593 np,
6594 ro,
6595 ri,
6596 rs,
6597 fr,
6598 hue,
6599 );
6600 return Ok(Value::Unit);
6601 },
6602
6603 "vtex_spiral" | "ลายเกลียว" | "纹螺" | "螺旋" | "나선" | "الگوی_مارپیچ" | "نقش_حلزوني" | "דוגמת_ספירלה" | "نقش_سرپیچ" | "motif_spirale" | "muster_spirale" | "узор_спираль" => {
6605 let cx = self.arg_num(&args, 0, 0.)? as f32;
6606 let cy = self.arg_num(&args, 1, 0.)? as f32;
6607 let cz = self.arg_num(&args, 2, 0.)? as f32;
6608 let ux = self.arg_num(&args, 3, 1.)? as f32;
6609 let uy = self.arg_num(&args, 4, 0.)? as f32;
6610 let uz = self.arg_num(&args, 5, 0.)? as f32;
6611 let vx = self.arg_num(&args, 6, 0.)? as f32;
6612 let vy = self.arg_num(&args, 7, 0.)? as f32;
6613 let vz = self.arg_num(&args, 8, 1.)? as f32;
6614 let nt = self.arg_num(&args, 9, 3.)? as f32;
6615 let mr = self.arg_num(&args, 10, 3.)? as f32;
6616 let st = self.arg_num(&args, 11, 120.)? as usize;
6617 let fr = self.arg_num(&args, 12, 0.)? as f32;
6618 let hue = self.arg_num(&args, 13, 0.)? as f32;
6619 let mut gfx = self.gfx.borrow_mut();
6620 let cam = gfx.camera.clone();
6621 crate::gfx::vtex::draw_spiral(
6622 &mut gfx.depth_queue,
6623 &cam,
6624 cx,
6625 cy,
6626 cz,
6627 ux,
6628 uy,
6629 uz,
6630 vx,
6631 vy,
6632 vz,
6633 nt,
6634 mr,
6635 st,
6636 fr,
6637 hue,
6638 );
6639 return Ok(Value::Unit);
6640 },
6641
6642 "vtex_flower" | "ลายดอก" | "纹花" | "花模様" | "꽃무늬" | "الگوی_گل" | "نقش_زهرة" | "דוגמת_פרח" | "نقش_پھول" | "motif_fleur" | "muster_blume" | "узор_цветок" => {
6644 let cx = self.arg_num(&args, 0, 0.)? as f32;
6645 let cy = self.arg_num(&args, 1, 0.)? as f32;
6646 let cz = self.arg_num(&args, 2, 0.)? as f32;
6647 let ux = self.arg_num(&args, 3, 1.)? as f32;
6648 let uy = self.arg_num(&args, 4, 0.)? as f32;
6649 let uz = self.arg_num(&args, 5, 0.)? as f32;
6650 let vx = self.arg_num(&args, 6, 0.)? as f32;
6651 let vy = self.arg_num(&args, 7, 0.)? as f32;
6652 let vz = self.arg_num(&args, 8, 1.)? as f32;
6653 let r = self.arg_num(&args, 9, 1.)? as f32;
6654 let ns = self.arg_num(&args, 10, 24.)? as usize;
6655 let fr = self.arg_num(&args, 11, 0.)? as f32;
6656 let hue = self.arg_num(&args, 12, 0.)? as f32;
6657 let mut gfx = self.gfx.borrow_mut();
6658 let cam = gfx.camera.clone();
6659 crate::gfx::vtex::draw_flower(
6660 &mut gfx.depth_queue,
6661 &cam,
6662 cx,
6663 cy,
6664 cz,
6665 ux,
6666 uy,
6667 uz,
6668 vx,
6669 vy,
6670 vz,
6671 r,
6672 ns,
6673 fr,
6674 hue,
6675 );
6676 return Ok(Value::Unit);
6677 },
6678
6679 "vtex_letter_rain" | "ลายอักษรไหล" | "纹字雨" | "文字雨" | "글자비" | "الگوی_باران_حروف" | "نقش_مطر_الحروف" | "דוגמת_גשם_אותיות" | "نقش_حروف_بارش" | "motif_pluie_lettres" | "muster_buchstabenregen" | "узор_дождь_букв" =>
6681 {
6682 let cx = self.arg_num(&args, 0, 0.)? as f32;
6683 let cy = self.arg_num(&args, 1, 0.)? as f32;
6684 let cz = self.arg_num(&args, 2, 0.)? as f32;
6685 let ux = self.arg_num(&args, 3, 1.)? as f32;
6686 let uy = self.arg_num(&args, 4, 0.)? as f32;
6687 let uz = self.arg_num(&args, 5, 0.)? as f32;
6688 let vx = self.arg_num(&args, 6, 0.)? as f32;
6689 let vy = self.arg_num(&args, 7, 0.)? as f32;
6690 let vz = self.arg_num(&args, 8, 1.)? as f32;
6691 let nc = self.arg_num(&args, 9, 16.)? as usize;
6692 let nv = self.arg_num(&args, 10, 14.)? as usize;
6693 let cw = self.arg_num(&args, 11, 0.65)? as f32;
6694 let rh = self.arg_num(&args, 12, 0.60)? as f32;
6695 let sp = self.arg_num(&args, 13, 0.025)? as f32;
6696 let fr = self.arg_num(&args, 14, 0.)? as f32;
6697 let hue = self.arg_num(&args, 15, 0.)? as f32;
6698 let mut gfx = self.gfx.borrow_mut();
6699 let cam = gfx.camera.clone();
6700 crate::gfx::vtex::draw_letter_rain(
6701 &mut gfx.depth_queue,
6702 &cam,
6703 cx,
6704 cy,
6705 cz,
6706 ux,
6707 uy,
6708 uz,
6709 vx,
6710 vy,
6711 vz,
6712 nc,
6713 nv,
6714 cw,
6715 rh,
6716 sp,
6717 fr,
6718 hue,
6719 );
6720 return Ok(Value::Unit);
6721 },
6722
6723 "vtex_hyperbolic_uv" | "ลายไฮเพอร์โบลิก" | "纹曲面" | "双曲線" | "쌍곡선" | "الگوی_هذلولی" | "نقش_زائدي" | "דוגמת_היפרבולית" | "نقش_ہائپربولک" | "motif_uv_hyperbolique" | "muster_hyperbolische_uv" | "узор_гиперболический_uv" =>
6725 {
6726 let cx = self.arg_num(&args, 0, 0.)? as f32;
6727 let cy = self.arg_num(&args, 1, 0.)? as f32;
6728 let cz = self.arg_num(&args, 2, 0.)? as f32;
6729 let ux = self.arg_num(&args, 3, 1.)? as f32;
6730 let uy = self.arg_num(&args, 4, 0.)? as f32;
6731 let uz = self.arg_num(&args, 5, 0.)? as f32;
6732 let vx = self.arg_num(&args, 6, 0.)? as f32;
6733 let vy = self.arg_num(&args, 7, 0.)? as f32;
6734 let vz = self.arg_num(&args, 8, 1.)? as f32;
6735 let mr = self.arg_num(&args, 9, 5.)? as f32;
6736 let nc = self.arg_num(&args, 10, 12.)? as usize;
6737 let nr = self.arg_num(&args, 11, 18.)? as usize;
6738 let fr = self.arg_num(&args, 12, 0.)? as f32;
6739 let hue = self.arg_num(&args, 13, 0.)? as f32;
6740 let mut gfx = self.gfx.borrow_mut();
6741 let cam = gfx.camera.clone();
6742 crate::gfx::vtex::draw_hyperbolic_uv(
6743 &mut gfx.depth_queue,
6744 &cam,
6745 cx,
6746 cy,
6747 cz,
6748 ux,
6749 uy,
6750 uz,
6751 vx,
6752 vy,
6753 vz,
6754 mr,
6755 nc,
6756 nr,
6757 fr,
6758 hue,
6759 );
6760 return Ok(Value::Unit);
6761 },
6762
6763 "vtex_halftone" | "ลายจุด" | "纹半调" | "網点模様" | "망점" | "الگوی_نیمتن" | "نقش_نصفي" | "דוגמת_חצי_גוון" | "نقش_ہاف_ٹون" | "motif_demi_ton" | "muster_halbton" | "узор_растр" => {
6765 let cx = self.arg_num(&args, 0, 0.)? as f32;
6766 let cy = self.arg_num(&args, 1, 0.)? as f32;
6767 let cz = self.arg_num(&args, 2, 0.)? as f32;
6768 let ux = self.arg_num(&args, 3, 1.)? as f32;
6769 let uy = self.arg_num(&args, 4, 0.)? as f32;
6770 let uz = self.arg_num(&args, 5, 0.)? as f32;
6771 let vx = self.arg_num(&args, 6, 0.)? as f32;
6772 let vy = self.arg_num(&args, 7, 0.)? as f32;
6773 let vz = self.arg_num(&args, 8, 1.)? as f32;
6774 let cols = self.arg_num(&args, 9, 16.)? as usize;
6775 let rows = self.arg_num(&args, 10, 12.)? as usize;
6776 let cw = self.arg_num(&args, 11, 0.5)? as f32;
6777 let ch = self.arg_num(&args, 12, 0.5)? as f32;
6778 let dens = self.arg_num(&args, 13, 0.4)? as f32;
6779 let fr = self.arg_num(&args, 14, 0.)? as f32;
6780 let hue = self.arg_num(&args, 15, 0.)? as f32;
6781 let mut gfx = self.gfx.borrow_mut();
6782 let cam = gfx.camera.clone();
6783 crate::gfx::vtex::draw_halftone(
6784 &mut gfx.depth_queue,
6785 &cam,
6786 cx,
6787 cy,
6788 cz,
6789 ux,
6790 uy,
6791 uz,
6792 vx,
6793 vy,
6794 vz,
6795 cols,
6796 rows,
6797 cw,
6798 ch,
6799 dens,
6800 fr,
6801 hue,
6802 );
6803 return Ok(Value::Unit);
6804 },
6805
6806 "vtex_tessellated" | "ลายตาข่าย" | "纹镶嵌" | "網目模様" | "격자망" | "الگوی_کاشیکاری" | "نقش_مرصوف_متكرر" | "דוגמת_ריצוף_חוזר" | "نقش_ٹائلنگ" | "motif_tesselle" | "muster_tessellation" | "узор_мозаика" =>
6808 {
6809 let cx = self.arg_num(&args, 0, 0.)? as f32;
6810 let cy = self.arg_num(&args, 1, 0.)? as f32;
6811 let cz = self.arg_num(&args, 2, 0.)? as f32;
6812 let ux = self.arg_num(&args, 3, 1.)? as f32;
6813 let uy = self.arg_num(&args, 4, 0.)? as f32;
6814 let uz = self.arg_num(&args, 5, 0.)? as f32;
6815 let vx = self.arg_num(&args, 6, 0.)? as f32;
6816 let vy = self.arg_num(&args, 7, 0.)? as f32;
6817 let vz = self.arg_num(&args, 8, 1.)? as f32;
6818 let cols = self.arg_num(&args, 9, 14.)? as usize;
6819 let rows = self.arg_num(&args, 10, 10.)? as usize;
6820 let cell = self.arg_num(&args, 11, 0.6)? as f32;
6821 let amp = self.arg_num(&args, 12, 0.25)? as f32;
6822 let freq = self.arg_num(&args, 13, 4.)? as f32;
6823 let fr = self.arg_num(&args, 14, 0.)? as f32;
6824 let hue = self.arg_num(&args, 15, 0.)? as f32;
6825 let mut gfx = self.gfx.borrow_mut();
6826 let cam = gfx.camera.clone();
6827 crate::gfx::vtex::draw_tessellated(
6828 &mut gfx.depth_queue,
6829 &cam,
6830 cx,
6831 cy,
6832 cz,
6833 ux,
6834 uy,
6835 uz,
6836 vx,
6837 vy,
6838 vz,
6839 cols,
6840 rows,
6841 cell,
6842 amp,
6843 freq,
6844 fr,
6845 hue,
6846 );
6847 return Ok(Value::Unit);
6848 },
6849
6850 "vtex_lotus" | "ลายดอกบัว" | "纹莲" | "蓮模様" | "연꽃무늬" | "الگوی_لوتوس" | "نقش_لوتس" | "דוגמת_לוטוס" | "نقش_کنول" | "motif_lotus" | "muster_lotus" | "узор_лотос" =>
6852 {
6853 let cx = self.arg_num(&args, 0, 0.)? as f32;
6854 let cy = self.arg_num(&args, 1, 0.)? as f32;
6855 let cz = self.arg_num(&args, 2, 0.)? as f32;
6856 let ux = self.arg_num(&args, 3, 1.)? as f32;
6857 let uy = self.arg_num(&args, 4, 0.)? as f32;
6858 let uz = self.arg_num(&args, 5, 0.)? as f32;
6859 let vx = self.arg_num(&args, 6, 0.)? as f32;
6860 let vy = self.arg_num(&args, 7, 0.)? as f32;
6861 let vz = self.arg_num(&args, 8, 1.)? as f32;
6862 let ri = self.arg_num(&args, 9, 1.)? as f32;
6863 let ro = self.arg_num(&args, 10, 2.)? as f32;
6864 let np = self.arg_num(&args, 11, 12.)? as usize;
6865 let fr = self.arg_num(&args, 12, 0.)? as f32;
6866 let hue = self.arg_num(&args, 13, 0.)? as f32;
6867 let mut gfx = self.gfx.borrow_mut();
6868 let cam = gfx.camera.clone();
6869 crate::gfx::vtex::draw_lotus(
6870 &mut gfx.depth_queue,
6871 &cam,
6872 cx,
6873 cy,
6874 cz,
6875 ux,
6876 uy,
6877 uz,
6878 vx,
6879 vy,
6880 vz,
6881 ri,
6882 ro,
6883 np,
6884 fr,
6885 hue,
6886 );
6887 return Ok(Value::Unit);
6888 },
6889
6890 "vtex_chakra" | "ลายจักร" | "纹轮" | "輪模様" | "바퀴무늬" | "الگوی_چاکرا" | "نقش_تشاكرا" | "דוגמת_צ'אקרה" | "نقش_چکر" | "motif_chakra" | "muster_chakra" | "узор_чакра" => {
6892 let cx = self.arg_num(&args, 0, 0.)? as f32;
6893 let cy = self.arg_num(&args, 1, 0.)? as f32;
6894 let cz = self.arg_num(&args, 2, 0.)? as f32;
6895 let ux = self.arg_num(&args, 3, 1.)? as f32;
6896 let uy = self.arg_num(&args, 4, 0.)? as f32;
6897 let uz = self.arg_num(&args, 5, 0.)? as f32;
6898 let vx = self.arg_num(&args, 6, 0.)? as f32;
6899 let vy = self.arg_num(&args, 7, 0.)? as f32;
6900 let vz = self.arg_num(&args, 8, 1.)? as f32;
6901 let r = self.arg_num(&args, 9, 2.)? as f32;
6902 let ns = self.arg_num(&args, 10, 8.)? as usize;
6903 let fr = self.arg_num(&args, 11, 0.)? as f32;
6904 let hue = self.arg_num(&args, 12, 0.)? as f32;
6905 let mut gfx = self.gfx.borrow_mut();
6906 let cam = gfx.camera.clone();
6907 crate::gfx::vtex::draw_chakra(
6908 &mut gfx.depth_queue,
6909 &cam,
6910 cx,
6911 cy,
6912 cz,
6913 ux,
6914 uy,
6915 uz,
6916 vx,
6917 vy,
6918 vz,
6919 r,
6920 ns,
6921 fr,
6922 hue,
6923 );
6924 return Ok(Value::Unit);
6925 },
6926
6927 "vtex_yantra" | "ลายยันต์" | "纹咒" | "護符模様" | "부적무늬" | "الگوی_یانترا" | "نقش_يانترا" | "דוגמת_יאנטרה" | "نقش_ینترا" | "motif_yantra" | "muster_yantra" | "узор_янтра" =>
6929 {
6930 let cx = self.arg_num(&args, 0, 0.)? as f32;
6931 let cy = self.arg_num(&args, 1, 0.)? as f32;
6932 let cz = self.arg_num(&args, 2, 0.)? as f32;
6933 let ux = self.arg_num(&args, 3, 1.)? as f32;
6934 let uy = self.arg_num(&args, 4, 0.)? as f32;
6935 let uz = self.arg_num(&args, 5, 0.)? as f32;
6936 let vx = self.arg_num(&args, 6, 0.)? as f32;
6937 let vy = self.arg_num(&args, 7, 0.)? as f32;
6938 let vz = self.arg_num(&args, 8, 1.)? as f32;
6939 let nl = self.arg_num(&args, 9, 4.)? as usize;
6940 let mr = self.arg_num(&args, 10, 3.)? as f32;
6941 let fr = self.arg_num(&args, 11, 0.)? as f32;
6942 let hue = self.arg_num(&args, 12, 0.)? as f32;
6943 let mut gfx = self.gfx.borrow_mut();
6944 let cam = gfx.camera.clone();
6945 crate::gfx::vtex::draw_yantra(
6946 &mut gfx.depth_queue,
6947 &cam,
6948 cx,
6949 cy,
6950 cz,
6951 ux,
6952 uy,
6953 uz,
6954 vx,
6955 vy,
6956 vz,
6957 nl,
6958 mr,
6959 fr,
6960 hue,
6961 );
6962 return Ok(Value::Unit);
6963 },
6964
6965 "vtex_spiked_cog" | "ฟันเฟืองหนาม" | "纹棘轮" | "歯車模様" | "톱니바퀴" | "الگوی_چرخدنده_خاردار" | "نقش_ترس_شائك" | "דוגמת_גלגל_קוצני" | "نقش_خاردار_گیئر" | "motif_engrenage_pointes" | "muster_stachelzahnrad" | "узор_шестерня_шипы" =>
6967 {
6968 let cx = self.arg_num(&args, 0, 0.)? as f32;
6969 let cy = self.arg_num(&args, 1, 0.)? as f32;
6970 let cz = self.arg_num(&args, 2, 0.)? as f32;
6971 let ux = self.arg_num(&args, 3, 1.)? as f32;
6972 let uy = self.arg_num(&args, 4, 0.)? as f32;
6973 let uz = self.arg_num(&args, 5, 0.)? as f32;
6974 let vx = self.arg_num(&args, 6, 0.)? as f32;
6975 let vy = self.arg_num(&args, 7, 0.)? as f32;
6976 let vz = self.arg_num(&args, 8, 1.)? as f32;
6977 let nt = self.arg_num(&args, 9, 12.)? as usize;
6978 let rb = self.arg_num(&args, 10, 1.)? as f32;
6979 let rs = self.arg_num(&args, 11, 1.3)? as f32;
6980 let rh = self.arg_num(&args, 12, 0.2)? as f32;
6981 let ns = self.arg_num(&args, 13, 6.)? as usize;
6982 let fr = self.arg_num(&args, 14, 0.)? as f32;
6983 let hue = self.arg_num(&args, 15, 0.)? as f32;
6984 let mut gfx = self.gfx.borrow_mut();
6985 let cam = gfx.camera.clone();
6986 crate::gfx::vtex::draw_spiked_cog(
6987 &mut gfx.depth_queue,
6988 &cam,
6989 cx,
6990 cy,
6991 cz,
6992 ux,
6993 uy,
6994 uz,
6995 vx,
6996 vy,
6997 vz,
6998 nt,
6999 rb,
7000 rs,
7001 rh,
7002 ns,
7003 fr,
7004 hue,
7005 );
7006 return Ok(Value::Unit);
7007 },
7008
7009 "vtex_torii" | "ประตูโทริอิ" | "纹鸟居" | "鳥居" | "도리이" | "الگوی_توری_ژاپنی" | "نقش_توري" | "דוגמת_טוריי" | "نقش_توری_گیٹ" | "motif_torii" | "muster_torii" | "узор_тории" =>
7011 {
7012 let cx = self.arg_num(&args, 0, 0.)? as f32;
7013 let cy = self.arg_num(&args, 1, 0.)? as f32;
7014 let cz = self.arg_num(&args, 2, 0.)? as f32;
7015 let ux = self.arg_num(&args, 3, 1.)? as f32;
7016 let uy = self.arg_num(&args, 4, 0.)? as f32;
7017 let uz = self.arg_num(&args, 5, 0.)? as f32;
7018 let vx = self.arg_num(&args, 6, 0.)? as f32;
7019 let vy = self.arg_num(&args, 7, 0.)? as f32;
7020 let vz = self.arg_num(&args, 8, 1.)? as f32;
7021 let w = self.arg_num(&args, 9, 4.)? as f32;
7022 let h = self.arg_num(&args, 10, 5.)? as f32;
7023 let fr = self.arg_num(&args, 11, 0.)? as f32;
7024 let hue = self.arg_num(&args, 12, 0.)? as f32;
7025 let mut gfx = self.gfx.borrow_mut();
7026 let cam = gfx.camera.clone();
7027 crate::gfx::vtex::draw_torii(
7028 &mut gfx.depth_queue,
7029 &cam,
7030 cx,
7031 cy,
7032 cz,
7033 ux,
7034 uy,
7035 uz,
7036 vx,
7037 vy,
7038 vz,
7039 w,
7040 h,
7041 fr,
7042 hue,
7043 );
7044 return Ok(Value::Unit);
7045 },
7046
7047 "vtex_pagoda" | "เจดีย์" | "纹塔" | "塔" | "탑" | "الگوی_پاگودا" | "نقش_باغودا" | "דוגמת_פגודה" | "نقش_پگوڈا" | "motif_pagode" | "muster_pagode" | "узор_пагода" => {
7049 let cx = self.arg_num(&args, 0, 0.)? as f32;
7050 let cy = self.arg_num(&args, 1, 0.)? as f32;
7051 let cz = self.arg_num(&args, 2, 0.)? as f32;
7052 let ux = self.arg_num(&args, 3, 1.)? as f32;
7053 let uy = self.arg_num(&args, 4, 0.)? as f32;
7054 let uz = self.arg_num(&args, 5, 0.)? as f32;
7055 let vx = self.arg_num(&args, 6, 0.)? as f32;
7056 let vy = self.arg_num(&args, 7, 0.)? as f32;
7057 let vz = self.arg_num(&args, 8, 1.)? as f32;
7058 let nt = self.arg_num(&args, 9, 5.)? as usize;
7059 let bw = self.arg_num(&args, 10, 2.)? as f32;
7060 let th = self.arg_num(&args, 11, 1.)? as f32;
7061 let tp = self.arg_num(&args, 12, 0.72)? as f32;
7062 let eo = self.arg_num(&args, 13, 0.28)? as f32;
7063 let fr = self.arg_num(&args, 14, 0.)? as f32;
7064 let hue = self.arg_num(&args, 15, 0.)? as f32;
7065 let mut gfx = self.gfx.borrow_mut();
7066 let cam = gfx.camera.clone();
7067 crate::gfx::vtex::draw_pagoda(
7068 &mut gfx.depth_queue,
7069 &cam,
7070 cx,
7071 cy,
7072 cz,
7073 ux,
7074 uy,
7075 uz,
7076 vx,
7077 vy,
7078 vz,
7079 nt,
7080 bw,
7081 th,
7082 tp,
7083 eo,
7084 fr,
7085 hue,
7086 );
7087 return Ok(Value::Unit);
7088 },
7089
7090 #[cfg(not(target_arch = "wasm32"))]
7096 "audio_tone"
7097 | "เสียงโทน"
7098 | "音调"
7099 | "音調"
7100 | "음조"
7101 | "空间音"
7102 | "空間音"
7103 | "공간음" | "تن_صدا" | "نغمة" | "צליל" | "آواز_کا_سر" | "tonalité_audio" | "audioton" | "звук_тон" => {
7104 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7105 let x = self.arg_num(&args, 1, 0.0)? as f32;
7106 let y = self.arg_num(&args, 2, 0.0)? as f32;
7107 let z = self.arg_num(&args, 3, 0.0)? as f32;
7108 let w = self.arg_num(&args, 4, 1.0)? as f32;
7109 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7110 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7111 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7112 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7113 if let Some(audio) = &self.audio {
7114 audio.set_tone(
7115 idx,
7116 ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth },
7117 );
7118 }
7119 return Ok(Value::Unit);
7120 },
7121
7122 #[cfg(not(target_arch = "wasm32"))]
7123 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" | "شنونده_صدا" | "مستمع_الصوت" | "מאזין_קול" | "آواز_سننے_والا" | "auditeur_audio" | "audiohörer" | "звук_слушатель" =>
7124 {
7125 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7126 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7127 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7128 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7129 if let Some(audio) = &self.audio {
7130 audio.set_listener(cry, sry, crx, srx);
7131 }
7132 return Ok(Value::Unit);
7133 },
7134
7135 #[cfg(not(target_arch = "wasm32"))]
7136 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" | "موسیقی_پسزمینه" | "موسيقى_خلفية" | "מוזיקת_רקע" | "پس_منظر_موسیقی" | "musique_fond" | "hintergrundmusik" | "фоновая_музыка" =>
7137 {
7138 let path = match args.first() {
7139 Some(Value::Str(s)) => s.clone(),
7140 _ => return Ok(Value::Unit),
7141 };
7142 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7143 if let Some(audio) = &self.audio {
7144 audio.load_bgm(&path, vol);
7145 }
7146 return Ok(Value::Unit);
7147 },
7148
7149 #[cfg(not(target_arch = "wasm32"))]
7150 "audio_bgm_volume"
7151 | "ระดับเสียงพื้นหลัง"
7152 | "ระดับเพลงประกอบ"
7153 | "背景乐音量"
7154 | "BGM音量"
7155 | "배경음악음량" | "بلندی_موسیقی_پسزمینه" | "مستوى_موسيقى_الخلفية" | "עוצמת_מוזיקת_רקע" | "پس_منظر_موسیقی_شدت" | "volume_musique_fond" | "hintergrundmusiklautstärke" | "громкость_фоновой_музыки" => {
7156 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7157 if let Some(audio) = &self.audio {
7158 audio.set_bgm_volume(vol);
7159 }
7160 return Ok(Value::Unit);
7161 },
7162
7163 #[cfg(not(target_arch = "wasm32"))]
7164 "audio_volume" | "ระดับเสียง" | "音量" | "음량" | "بلندی_صدا" | "مستوى_الصوت" | "עוצמת_קול" | "آواز_کی_شدت" | "volume_audio" | "audiolautstärke" | "звук_громкость" => {
7165 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7166 if let Some(audio) = &self.audio {
7167 audio.set_master_volume(vol);
7168 }
7169 return Ok(Value::Unit);
7170 },
7171
7172 #[cfg(target_arch = "wasm32")]
7174 "audio_tone"
7175 | "เสียงโทน"
7176 | "音调"
7177 | "音調"
7178 | "음조"
7179 | "空间音"
7180 | "空間音"
7181 | "공간음" | "تن_صدا" | "نغمة" | "צליל" | "آواز_کا_سر" | "tonalité_audio" | "audioton" | "звук_тон" => {
7182 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7183 let x = self.arg_num(&args, 1, 0.0)? as f32;
7184 let y = self.arg_num(&args, 2, 0.0)? as f32;
7185 let z = self.arg_num(&args, 3, 0.0)? as f32;
7186 let w = self.arg_num(&args, 4, 1.0)? as f32;
7187 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7188 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7189 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7190 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7191 crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
7192 return Ok(Value::Unit);
7193 },
7194
7195 #[cfg(target_arch = "wasm32")]
7196 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" | "شنونده_صدا" | "مستمع_الصوت" | "מאזין_קול" | "آواز_سننے_والا" | "auditeur_audio" | "audiohörer" | "звук_слушатель" =>
7197 {
7198 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7199 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7200 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7201 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7202 crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
7203 return Ok(Value::Unit);
7204 },
7205
7206 #[cfg(target_arch = "wasm32")]
7207 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" | "موسیقی_پسزمینه" | "موسيقى_خلفية" | "מוזיקת_רקע" | "پس_منظر_موسیقی" | "musique_fond" | "hintergrundmusik" | "фоновая_музыка" =>
7208 {
7209 let path = self.arg_str(&args, 0, "");
7210 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7211 crate::gfx::audio_web::load_bgm(&path, vol);
7212 return Ok(Value::Unit);
7213 },
7214
7215 #[cfg(target_arch = "wasm32")]
7216 "audio_bgm_volume"
7217 | "ระดับเสียงพื้นหลัง"
7218 | "ระดับเพลงประกอบ"
7219 | "背景乐音量"
7220 | "BGM音量"
7221 | "배경음악음량" | "بلندی_موسیقی_پسزمینه" | "مستوى_موسيقى_الخلفية" | "עוצמת_מוזיקת_רקע" | "پس_منظر_موسیقی_شدت" | "volume_musique_fond" | "hintergrundmusiklautstärke" | "громкость_фоновой_музыки" => {
7222 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7223 crate::gfx::audio_web::set_bgm_volume(vol);
7224 return Ok(Value::Unit);
7225 },
7226
7227 #[cfg(target_arch = "wasm32")]
7228 "audio_volume" | "ระดับเสียง" | "音量" | "음량" | "بلندی_صدا" | "مستوى_الصوت" | "עוצמת_קול" | "آواز_کی_شدت" | "volume_audio" | "audiolautstärke" | "звук_громкость" => {
7229 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7230 crate::gfx::audio_web::set_master_volume(vol);
7231 return Ok(Value::Unit);
7232 },
7233
7234 #[cfg(target_arch = "wasm32")]
7236 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" | "بارگذاری_نمونه_صدا" | "تحميل_عينة_صوتية" | "טעינת_דגימת_קול" | "آواز_نمونہ_لوڈ" | "charger_échantillon" | "sample_laden" | "загрузить_семпл" =>
7237 {
7238 let path = self.arg_str(&args, 0, "");
7239 let resolved = self.wasm_resolve_source_path(&path);
7240 match wasm_fetch_bytes(&resolved)
7241 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
7242 {
7243 Ok(t) => {
7244 let id = crate::gfx::audio_web::add_sample(&t.stereo, t.channels, t.rate);
7245 return Ok(Value::Number(id as f64));
7246 },
7247 Err(e) => {
7248 eprintln!("audio_sample_load failed ({path}): {e}");
7249 return Ok(Value::Number(-1.0));
7250 },
7251 }
7252 },
7253 #[cfg(target_arch = "wasm32")]
7254 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" | "پخش_نمونه_صدا" | "تشغيل_عينة_صوتية" | "נגינת_דגימת_קול" | "آواز_نمونہ_چلاؤ" | "jouer_échantillon" | "sample_abspielen" | "играть_семпл" =>
7255 {
7256 let id = self.arg_num(&args, 0, 0.0)? as usize;
7257 let x = self.arg_num(&args, 1, 0.0)? as f32;
7258 let y = self.arg_num(&args, 2, 0.0)? as f32;
7259 let z = self.arg_num(&args, 3, 0.0)? as f32;
7260 let vol = self.arg_num(&args, 5, 1.0)? as f32;
7262 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
7263 crate::gfx::audio_web::play_sample(id, x, y, z, vol, looping);
7264 return Ok(Value::Number(0.0));
7265 },
7266 #[cfg(target_arch = "wasm32")]
7267 "audio_sample_stop"
7268 | "停止采样"
7269 | "サンプル停止"
7270 | "샘플정지"
7271 | "หยุดตัวอย่างเสียง"
7272 | "audio_fx_reverb"
7273 | "混响"
7274 | "リバーブ"
7275 | "리버브"
7276 | "เสียงก้อง"
7277 | "audio_fx_delay"
7278 | "回声"
7279 | "ディレイ効果"
7280 | "딜레이"
7281 | "เสียงสะท้อน"
7282 | "audio_fx_lowpass"
7283 | "低通滤波"
7284 | "ローパス"
7285 | "저역통과"
7286 | "กรองความถี่ต่ำ" | "توقف_نمونه_صدا" | "إيقاف_عينة_صوتية" | "עצירת_דגימת_קול" | "آواز_نمونہ_روکو" | "arrêter_échantillon" | "sample_stoppen" | "остановить_семпл" => {
7287 return Ok(Value::Unit);
7288 },
7289
7290 "รอหน้าต่าง" | "wait_window" | "gfx_wait" | "انتظار_پنجره" | "انتظر_النافذة" | "המתן_לחלון" | "ونڈو_انتظار" => {
7292 #[cfg(not(target_arch = "wasm32"))]
7293 loop {
7294 let still_open = {
7295 let gfx = self.gfx.borrow();
7296 gfx.window
7297 .as_ref()
7298 .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
7299 .unwrap_or(false)
7300 };
7301 if !still_open {
7302 break;
7303 }
7304 let (buf, w, h) = {
7305 let gfx = self.gfx.borrow();
7306 (gfx.buffer.clone(), gfx.width, gfx.height)
7307 };
7308 let mut gfx = self.gfx.borrow_mut();
7309 if let Some(win) = gfx.window.as_mut() {
7310 if win.update_with_buffer(&buf, w, h).is_err() {
7311 break;
7312 }
7313 }
7314 }
7315 return Ok(Value::Unit);
7316 },
7317
7318 "read_file" | "อ่านไฟล์" | "خواندن_فایل" | "اقرأ_الملف" | "קרא_קובץ" | "فائل_پڑھو" => {
7320 #[cfg(target_arch = "wasm32")]
7321 return Ok(Value::Str(String::new()));
7322 #[cfg(not(target_arch = "wasm32"))]
7323 {
7324 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7325 return std::fs::read_to_string(&path)
7326 .map(Value::Str)
7327 .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
7328 }
7329 },
7330 #[cfg(not(target_arch = "wasm32"))]
7332 "net_host" | "เน็ตโฮสต์" | "میزبانی_شبکه" | "استضف_الشبكة" | "ארח_רשת" | "نیٹ_ہوسٹ" => {
7333 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7334 net::host(port);
7335 return Ok(Value::Unit);
7336 },
7337 #[cfg(not(target_arch = "wasm32"))]
7338 "net_join" | "เน็ตจอย" | "پیوستن_شبکه" | "انضم_للشبكة" | "הצטרף_לרשת" | "نیٹ_شمولیت" => {
7339 let ip = self.arg_str(&args, 0, "127.0.0.1");
7340 let port = self.arg_num(&args, 1, 7777.0)? as u16;
7341 net::join(&ip, port);
7342 return Ok(Value::Unit);
7343 },
7344 #[cfg(not(target_arch = "wasm32"))]
7345 "net_send" | "เน็ตส่ง" | "ارسال_شبکه" | "أرسل_عبر_الشبكة" | "שלח_ברשת" | "نیٹ_بھیجو" => {
7346 let s = self.arg_str(&args, 0, "");
7347 net::send(&s);
7348 return Ok(Value::Unit);
7349 },
7350 #[cfg(not(target_arch = "wasm32"))]
7351 "net_recv" | "เน็ตรับ" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" => {
7352 return Ok(Value::Str(net::recv()));
7353 },
7354 #[cfg(not(target_arch = "wasm32"))]
7355 "net_status" | "เน็ตสถานะ" | "وضعیت_شبکه" | "حالة_الشبكة" | "סטטוס_רשת" | "نیٹ_حالت" => {
7356 return Ok(Value::Number(net::status() as f64));
7357 },
7358 #[cfg(not(target_arch = "wasm32"))]
7359 "net_recv_from" => {
7360 return Ok(Value::Str(net::recv_from()));
7361 },
7362 #[cfg(not(target_arch = "wasm32"))]
7363 "net_send_to" => {
7364 let id = self.arg_num(&args, 0, 0.0)? as u64;
7365 let s = self.arg_str(&args, 1, "");
7366 net::send_to(id, &s);
7367 return Ok(Value::Unit);
7368 },
7369 #[cfg(not(target_arch = "wasm32"))]
7370 "net_close" | "연결종료" => {
7371 net::close();
7372 return Ok(Value::Unit);
7373 },
7374 #[cfg(not(target_arch = "wasm32"))]
7376 "net_announce" | "เน็ตประกาศ" | "اعلام_شبکه" | "أعلن_في_الشبكة" | "הכרז_ברשת" | "نیٹ_اعلان" => {
7377 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7378 let info = self.arg_str(&args, 1, "");
7379 net::announce(port, &info);
7380 return Ok(Value::Unit);
7381 },
7382 #[cfg(not(target_arch = "wasm32"))]
7383 "net_announce_stop" | "เน็ตหยุดประกาศ" | "توقف_اعلام" | "أوقف_الإعلان" | "עצור_הכרזה" | "اعلان_روکو" => {
7384 net::announce_stop();
7385 return Ok(Value::Unit);
7386 },
7387 #[cfg(not(target_arch = "wasm32"))]
7388 "net_discover" | "เน็ตค้นหา" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" => {
7389 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7390 return Ok(Value::Str(net::discover(port)));
7391 },
7392 #[cfg(not(target_arch = "wasm32"))]
7393 "net_test" | "เน็ตทดสอบ" | "آزمون_شبکه" | "اختبر_الشبكة" | "בדוק_רשת" | "نیٹ_ٹیسٹ" => {
7394 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7395 return Ok(Value::Str(net::test_bind(port)));
7396 },
7397 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7399 "http_route" | "เว็บเส้นทาง" | "مسیر_HTTP" | "مسار_HTTP" | "נתיב_HTTP" | "HTTP_روٹ" => {
7400 let method = self.arg_str(&args, 0, "GET").to_uppercase();
7401 let path = self.arg_str(&args, 1, "/");
7402 let handler = args.get(2).cloned().unwrap_or(Value::Unit);
7403 self.http_routes.push((method, path, handler));
7404 return Ok(Value::Unit);
7405 },
7406 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7410 "http_static" | "เว็บสแตติก" | "فایل_ایستای_HTTP" | "ملفات_HTTP_ثابتة" | "קבצים_סטטיים_HTTP" | "HTTP_مستقل_فائل" => {
7411 let prefix = self.arg_str(&args, 0, "/static");
7412 let dir = self.arg_str(&args, 1, "static");
7413 self.http_static_dirs.push((prefix, dir));
7414 return Ok(Value::Unit);
7415 },
7416 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7417 "http_serve" | "เว็บเสิร์ฟ" | "سرویس_HTTP" | "قدّم_HTTP" | "הגש_HTTP" | "HTTP_سرو" => {
7418 let host = self.arg_str(&args, 0, "127.0.0.1");
7419 let port = self.arg_num(&args, 1, 8080.0)? as u16;
7420 let routes = std::mem::take(&mut self.http_routes);
7421 let static_dirs = std::mem::take(&mut self.http_static_dirs);
7422 let rx = web::spawn_server(host.clone(), port, static_dirs);
7428 for pending in rx {
7429 let matched = routes
7430 .iter()
7431 .find(|(m, p, _)| m == &pending.method && p == &pending.path);
7432 let response = match matched {
7433 Some((_, _, handler)) => {
7434 let req_value = Value::Struct {
7435 name: "Request".to_string(),
7436 fields: vec![
7437 ("method".to_string(), Value::Str(pending.method.clone())),
7438 ("path".to_string(), Value::Str(pending.path.clone())),
7439 ("query".to_string(), Value::Str(pending.query.clone())),
7440 ("body".to_string(), Value::Str(pending.body.clone())),
7441 ("cookie".to_string(), Value::Str(pending.cookie.clone())),
7442 (
7443 "authorization".to_string(),
7444 Value::Str(pending.authorization.clone()),
7445 ),
7446 (
7447 "client_ip".to_string(),
7448 Value::Str(pending.client_ip.clone()),
7449 ),
7450 ],
7451 };
7452 match self.call_value(handler.clone(), vec![req_value]) {
7453 Ok(v) => web::value_to_response(&v),
7454 Err(e) => web::HttpResponse {
7455 status: 500,
7456 content_type: "text/plain; charset=utf-8".to_string(),
7457 body: format!("handler error: {e:?}"),
7458 set_cookie: None,
7459 location: None,
7460 },
7461 }
7462 },
7463 None => web::HttpResponse {
7464 status: 404,
7465 content_type: "text/plain; charset=utf-8".to_string(),
7466 body: "not found".to_string(),
7467 set_cookie: None,
7468 location: None,
7469 },
7470 };
7471 let _ = pending.respond_to.send(response);
7472 }
7473 return Ok(Value::Unit);
7474 },
7475 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7479 "http_post_async" | "เว็บโพสต์ไม่บล็อก" | "ارسال_ناهمگام_HTTP" | "أرسل_HTTP_غير_متزامن" | "שלח_HTTP_אסינכרוני" | "HTTP_غیر_ہمزمان_بھیجو" => {
7480 let url = self.arg_str(&args, 0, "");
7481 let body = self.arg_str(&args, 1, "");
7482 let content_type = self.arg_str(&args, 2, "application/json");
7483 let id = self.async_jobs.start_post(url, content_type, body);
7484 return Ok(Value::Str(id));
7485 },
7486 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7490 "http_job_poll" | "เว็บงานสำรวจ" | "بررسی_وظیفه_HTTP" | "استطلع_مهمة_HTTP" | "בדוק_משימת_HTTP" | "HTTP_کام_پول" => {
7491 let id = self.arg_str(&args, 0, "");
7492 return Ok(Value::Str(self.async_jobs.poll(&id).unwrap_or_default()));
7493 },
7494 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7501 "sdai_generate_start" | "เอสดีเอไอเริ่มสร้าง" | "شروع_تولید_هوش" | "ابدأ_توليد_الذكاء" | "התחל_יצירת_בינה" | "اے_آئی_تخلیق_شروع" => {
7502 let base_url = self.arg_str(&args, 0, "http://127.0.0.1:1342");
7503 let prompt = self.arg_str(&args, 1, "");
7504 let width = self.arg_num(&args, 2, 512.0)? as u32;
7505 let height = self.arg_num(&args, 3, 512.0)? as u32;
7506 let id = self.async_jobs.start_sdai_txt2img(base_url, prompt, width, height);
7507 return Ok(Value::Str(id));
7508 },
7509 "query_param" | "พารามิเตอร์" | "پارامتر_پرسوجو" | "معامل_الاستعلام" | "פרמטר_שאילתה" | "کوئری_پیرامیٹر" => {
7511 let qs = self.arg_str(&args, 0, "");
7512 let name = self.arg_str(&args, 1, "");
7513 let default = self.arg_str(&args, 2, "");
7514 let mut found = default;
7515 for pair in qs.split('&') {
7516 let mut it = pair.splitn(2, '=');
7517 if it.next().unwrap_or("") == name {
7518 found = url_decode(it.next().unwrap_or(""));
7519 break;
7520 }
7521 }
7522 return Ok(Value::Str(found));
7523 },
7524 "cookie_get" | "รับคุกกี้" | "دریافت_کوکی" | "اجلب_الكعكة" | "קבל_עוגייה" | "کوکی_حاصل_کرو" => {
7526 let header = self.arg_str(&args, 0, "");
7527 let name = self.arg_str(&args, 1, "");
7528 let default = self.arg_str(&args, 2, "");
7529 let mut found = default;
7530 for pair in header.split(';') {
7531 let p = pair.trim();
7532 let mut it = p.splitn(2, '=');
7533 if it.next().unwrap_or("") == name {
7534 found = it.next().unwrap_or("").to_string();
7535 break;
7536 }
7537 }
7538 return Ok(Value::Str(found));
7539 },
7540 "html_escape" | "กันเอชทีเอ็มแอล" | "فرار_HTML" | "أفلت_HTML" | "בריחת_HTML" | "HTML_ایسکیپ" => {
7542 let s = self.arg_str(&args, 0, "");
7543 return Ok(Value::Str(
7544 s.replace('&', "&")
7545 .replace('<', "<")
7546 .replace('>', ">")
7547 .replace('"', """)
7548 .replace('\'', "'"),
7549 ));
7550 },
7551 "json_escape" | "หนีเจสัน" | "فرار_JSON" | "أفلت_JSON" | "בריחת_JSON" | "JSON_ایسکیپ" => {
7556 let s = self.arg_str(&args, 0, "");
7557 let mut out = String::with_capacity(s.len() + 8);
7558 for c in s.chars() {
7559 match c {
7560 '"' => out.push_str("\\\""),
7561 '\\' => out.push_str("\\\\"),
7562 '\n' => out.push_str("\\n"),
7563 '\r' => out.push_str("\\r"),
7564 '\t' => out.push_str("\\t"),
7565 c if (c as u32) < 0x20 => {
7566 out.push_str(&format!("\\u{:04x}", c as u32))
7567 },
7568 c => out.push(c),
7569 }
7570 }
7571 return Ok(Value::Str(out));
7572 },
7573 "cli_arg" | "อาร์กิวเมนต์" | "آرگومان_خطفرمان" | "معامل_سطر_الأوامر" | "ארגומנט_שורת_פקודה" | "سی_ایل_آئی_دلیل" => {
7575 let name = self.arg_str(&args, 0, "");
7576 let default = self.arg_str(&args, 1, "");
7577 let flag = format!("--{name}");
7578 let argv: Vec<String> = std::env::args().collect();
7579 let found = argv
7580 .iter()
7581 .position(|a| a == &flag)
7582 .and_then(|i| argv.get(i + 1))
7583 .cloned()
7584 .unwrap_or(default);
7585 return Ok(Value::Str(found));
7586 },
7587 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7589 "db_open" | "ฐานข้อมูลเปิด" | "باز_کردن_پایگاه_داده" | "افتح_قاعدة_البيانات" | "פתח_מסד_נתונים" | "ڈیٹا_بیس_کھولو" => {
7590 let path = self.arg_str(&args, 0, "app.db");
7591 let conn = ling_http::rusqlite::Connection::open(&path)
7592 .map_err(|e| EvalErr::from(format!("db_open '{path}': {e}")))?;
7593 let _ = conn.execute_batch("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;");
7594 self.db = Some(conn);
7595 return Ok(Value::Unit);
7596 },
7597 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7600 "db_exec" | "ฐานข้อมูลรัน" | "اجرای_پایگاه_داده" | "نفّذ_في_قاعدة_البيانات" | "בצע_במסד_נתונים" | "ڈیٹا_بیس_عمل" => {
7601 let sql = self.arg_str(&args, 0, "");
7602 let params = values_to_sql_params(&args[1.min(args.len())..]);
7603 let conn = self
7604 .db
7605 .as_ref()
7606 .ok_or_else(|| EvalErr::from("db_exec: call db_open first".to_string()))?;
7607 let n = conn
7608 .execute(
7609 &sql,
7610 ling_http::rusqlite::params_from_iter(params.iter()),
7611 )
7612 .map_err(|e| EvalErr::from(format!("db_exec: {e}\n sql: {sql}")))?;
7613 return Ok(Value::Number(n as f64));
7614 },
7615 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7617 "db_query" | "ฐานข้อมูลถาม" | "پرسوجوی_پایگاه_داده" | "استعلم_قاعدة_البيانات" | "שאילתת_מסד_נתונים" | "ڈیٹا_بیس_سوال" => {
7618 let sql = self.arg_str(&args, 0, "");
7619 let params = values_to_sql_params(&args[1.min(args.len())..]);
7620 let conn = self
7621 .db
7622 .as_ref()
7623 .ok_or_else(|| EvalErr::from("db_query: call db_open first".to_string()))?;
7624 let mut stmt = conn
7625 .prepare(&sql)
7626 .map_err(|e| EvalErr::from(format!("db_query: {e}\n sql: {sql}")))?;
7627 let col_names: Vec<String> =
7628 stmt.column_names().iter().map(|s| s.to_string()).collect();
7629 let mut rows = stmt
7630 .query(ling_http::rusqlite::params_from_iter(params.iter()))
7631 .map_err(|e| EvalErr::from(format!("db_query: {e}")))?;
7632 let mut out = Vec::new();
7633 while let Some(row) = rows
7634 .next()
7635 .map_err(|e| EvalErr::from(format!("db_query row: {e}")))?
7636 {
7637 let mut fields = Vec::with_capacity(col_names.len());
7638 for (i, col) in col_names.iter().enumerate() {
7639 use ling_http::rusqlite::types::ValueRef;
7640 let v = match row.get_ref(i) {
7641 Ok(ValueRef::Null) => Value::Str(String::new()),
7642 Ok(ValueRef::Integer(n)) => Value::Number(n as f64),
7643 Ok(ValueRef::Real(n)) => Value::Number(n),
7644 Ok(ValueRef::Text(t)) => {
7645 Value::Str(String::from_utf8_lossy(t).into_owned())
7646 },
7647 Ok(ValueRef::Blob(b)) =>
7648
7649 {
7650 use base64::Engine as _;
7651 Value::Str(base64::engine::general_purpose::STANDARD.encode(b))
7652 },
7653 Err(_) => Value::Str(String::new()),
7654 };
7655 fields.push((col.clone(), v));
7656 }
7657 out.push(Value::Struct { name: "Row".to_string(), fields });
7658 }
7659 return Ok(Value::List(Rc::new(out)));
7660 },
7661 #[cfg(not(target_arch = "wasm32"))]
7663 "gamepad_poll" | "จอยโพล" | "بررسی_دسته_بازی" | "استطلع_يد_اللعب" | "בדוק_בקר_משחק" | "گیم_پیڈ_پول_کرو" => {
7664 gamepad::poll();
7665 return Ok(Value::Unit);
7666 },
7667 #[cfg(not(target_arch = "wasm32"))]
7668 "gamepad_button" | "จอยปุ่ม" | "دکمه_دسته_بازی" | "زر_يد_اللعب" | "כפתור_בקר_משחק" | "گیم_پیڈ_بٹن_دبایا" => {
7669 let name = self.arg_str(&args, 0, "");
7670 return Ok(Value::Number(if gamepad::button(&name) {
7671 1.0
7672 } else {
7673 0.0
7674 }));
7675 },
7676 #[cfg(not(target_arch = "wasm32"))]
7677 "gamepad_axis" | "จอยแกน" | "محور_دسته_بازی" | "محور_يد_اللعب" | "ציר_בקר_משחק" | "گیم_پیڈ_محور" => {
7678 let name = self.arg_str(&args, 0, "");
7679 return Ok(Value::Number(gamepad::axis(&name) as f64));
7680 },
7681 #[cfg(not(target_arch = "wasm32"))]
7682 "gamepad_rumble" | "จอยสั่น" | "لرزش_دسته_بازی" | "اهتزاز_يد_اللعب" | "רטט_בקר_משחק" | "گیم_پیڈ_لرزش" => {
7683 let low = self.arg_num(&args, 0, 0.0)? as f32;
7684 let high = self.arg_num(&args, 1, 0.0)? as f32;
7685 let ms = self.arg_num(&args, 2, 200.0)? as u32;
7686 gamepad::rumble(low, high, ms);
7687 return Ok(Value::Unit);
7688 },
7689 #[cfg(not(target_arch = "wasm32"))]
7690 "gamepad_list" | "จอยรายการ" | "فهرست_دستههای_بازی" | "قائمة_أيدي_اللعب" | "רשימת_בקרי_משחק" | "گیم_پیڈ_فہرست" => {
7691 return Ok(Value::Str(gamepad::list()));
7692 },
7693 #[cfg(not(target_arch = "wasm32"))]
7694 "gamepad_any" | "จอยใดๆ" | "هر_دسته_بازی" | "أي_يد_لعب" | "בקר_כלשהו" | "کوئی_بھی_گیم_پیڈ" => {
7695 return Ok(Value::Number(if gamepad::any_button() { 1.0 } else { 0.0 }));
7696 },
7697 #[cfg(target_arch = "wasm32")]
7699 "gamepad_poll" | "จอยโพล" | "gamepad_rumble" | "จอยสั่น" | "بررسی_دسته_بازی" | "استطلع_يد_اللعب" | "בדוק_בקר_משחק" | "گیم_پیڈ_پول_کرو" => {
7700 return Ok(Value::Unit);
7701 },
7702 #[cfg(target_arch = "wasm32")]
7703 "gamepad_button" | "จอยปุ่ม" | "gamepad_axis" | "จอยแกน" | "gamepad_any" | "จอยใดๆ" | "دکمه_دسته_بازی" | "زر_يد_اللعب" | "כפתור_בקר_משחק" | "گیم_پیڈ_بٹن" =>
7704 {
7705 return Ok(Value::Number(0.0));
7706 },
7707 #[cfg(target_arch = "wasm32")]
7708 "gamepad_list" | "จอยรายการ" | "فهرست_دستههای_بازی" | "قائمة_أيدي_اللعب" | "רשימת_בקרי_משחק" | "گیم_پیڈ_فہرست" => {
7709 return Ok(Value::Str(String::new()));
7710 },
7711
7712 #[cfg(not(target_arch = "wasm32"))]
7715 "nn_new" | "建神经网" | "ニューラル作成" | "신경망생성" | "สร้างโครงข่าย" | "شبکه_جدید" | "شبكة_جديدة" | "רשת_חדשה" | "نئی_نیورل_نیٹ" | "nouveau_réseau" | "neues_netz" | "новая_сеть" =>
7716 {
7717 let n_in = self.arg_num(&args, 0, 1.0)?.max(0.0) as usize;
7718 let seed = self.arg_num(&args, 1, 1.0)? as u64;
7719 return Ok(Value::Number(ai::nn_new(n_in, seed) as f64));
7720 },
7721 #[cfg(not(target_arch = "wasm32"))]
7723 "nn_dense" | "密集层" | "密層追加" | "밀집층" | "ชั้นหนาแน่น" | "لایه_متراکم" | "طبقة_كثيفة" | "שכבה_צפופה" | "ڈینس_لیئر" | "réseau_dense" | "netz_dicht" | "плотная_сеть" =>
7724 {
7725 let id = self.arg_num(&args, 0, -1.0)? as i64;
7726 let units = self.arg_num(&args, 1, 1.0)?.max(1.0) as usize;
7727 let act = self.arg_str(&args, 2, "relu");
7728 ai::nn_dense(id, units, &act);
7729 return Ok(Value::Unit);
7730 },
7731 #[cfg(not(target_arch = "wasm32"))]
7733 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" | "پیشروی_شبکه" | "تمرير_أمامي" | "העברה_קדימה" | "فارورڈ_پاس" | "propager_réseau" | "netz_vorwärts" | "прямой_проход_сети" =>
7734 {
7735 let id = self.arg_num(&args, 0, -1.0)? as i64;
7736 let input = self.arg_list_f32(&args, 1);
7737 let out = ai::nn_forward(id, &input);
7738 return Ok(Value::List(Rc::new(
7739 out.into_iter().map(|v| Value::Number(v as f64)).collect(),
7740 )));
7741 },
7742 #[cfg(not(target_arch = "wasm32"))]
7744 "nn_train" | "训练网" | "ニューラル学習" | "신경망학습" | "ฝึกโครงข่าย" | "آموزش_شبکه" | "درّب_الشبكة" | "אמן_רשת" | "نیٹ_ٹریننگ" | "entraîner_réseau" | "netz_trainieren" | "обучить_сеть" =>
7745 {
7746 let id = self.arg_num(&args, 0, -1.0)? as i64;
7747 let input = self.arg_list_f32(&args, 1);
7748 let target = self.arg_list_f32(&args, 2);
7749 let lr = self.arg_num(&args, 3, 0.01)? as f32;
7750 return Ok(Value::Number(ai::nn_train(id, &input, &target, lr) as f64));
7751 },
7752 #[cfg(not(target_arch = "wasm32"))]
7754 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" | "ذخیره_شبکه" | "احفظ_الشبكة" | "שמור_רשת" | "نیٹ_محفوظ_کرو" | "sauvegarder_réseau" | "netz_speichern" | "сохранить_сеть" =>
7755 {
7756 let id = self.arg_num(&args, 0, -1.0)? as i64;
7757 let path = self.arg_str(&args, 1, "model.lnn");
7758 return Ok(Value::Bool(ai::nn_save(id, &path)));
7759 },
7760 #[cfg(not(target_arch = "wasm32"))]
7762 "nn_load" | "载入网" | "網読込" | "신경망불러오기" | "โหลดโครงข่าย" | "بارگذاری_شبکه" | "حمّل_الشبكة" | "טען_רשת" | "نیٹ_لوڈ" | "charger_réseau" | "netz_laden" | "загрузить_сеть" =>
7763 {
7764 let path = self.arg_str(&args, 0, "model.lnn");
7765 return Ok(Value::Number(ai::nn_load(&path) as f64));
7766 },
7767
7768 #[cfg(not(target_arch = "wasm32"))]
7771 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" | "ساخت_درخت_رفتار" | "ابنِ_شجرة_السلوك" | "בנה_עץ_התנהגות" | "بی_ٹی_تعمیر" | "construire_arbre_comportement" | "verhaltensbaum_bauen" | "построить_дерево_поведения" =>
7772 {
7773 let spec = self.arg_str(&args, 0, "");
7774 return Ok(Value::Number(ai::bt_build(&spec) as f64));
7775 },
7776 #[cfg(not(target_arch = "wasm32"))]
7778 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" | "تنظیم_واقعیت" | "عيّن_حقيقة" | "קבע_עובדה" | "بی_ٹی_سیٹ" | "définir_arbre_comportement" | "verhaltensbaum_setzen" | "задать_дерево_поведения" =>
7779 {
7780 let id = self.arg_num(&args, 0, -1.0)? as i64;
7781 let key = self.arg_str(&args, 1, "");
7782 let val = self.arg_num(&args, 2, 0.0)? as f32;
7783 ai::bt_set(id, &key, val);
7784 return Ok(Value::Unit);
7785 },
7786 #[cfg(not(target_arch = "wasm32"))]
7788 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" | "تیک_درخت_رفتار" | "نبضة_شجرة_السلوك" | "טיק_עץ_התנהגות" | "بی_ٹی_ٹک" | "tick_arbre_comportement" | "verhaltensbaum_tick" | "тик_дерева_поведения" =>
7789 {
7790 let id = self.arg_num(&args, 0, -1.0)? as i64;
7791 return Ok(Value::Str(ai::bt_tick(id)));
7792 },
7793 #[cfg(not(target_arch = "wasm32"))]
7795 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" | "وضعیت_درخت_رفتار" | "حالة_شجرة_السلوك" | "סטטוס_עץ_התנהגות" | "بی_ٹی_حالت" | "statut_arbre_comportement" | "verhaltensbaum_status" | "статус_дерева_поведения" =>
7796 {
7797 let id = self.arg_num(&args, 0, -1.0)? as i64;
7798 return Ok(Value::Number(ai::bt_status(id) as f64));
7799 },
7800
7801 #[cfg(not(target_arch = "wasm32"))]
7804 "dialog_new" | "建对话模型" | "対話モデル作成" | "대화모델생성" | "สร้างโมเดลสนทนา" | "مدل_گفتگوی_جدید" | "نموذج_حوار_جديد" | "מודל_דיאלוג_חדש" | "نیا_مکالمہ_ماڈل" | "nouveau_dialogue" | "neuer_dialog" | "новый_диалог" =>
7805 {
7806 let ctx = self.arg_num(&args, 0, 3.0)?.max(1.0) as usize;
7807 let embed = self.arg_num(&args, 1, 32.0)?.max(1.0) as usize;
7808 let hidden = self.arg_num(&args, 2, 64.0)?.max(1.0) as usize;
7809 let seed = self.arg_num(&args, 3, 1.0)? as u64;
7810 return Ok(Value::Number(
7811 ai::dialog_new(ctx, embed, hidden, seed) as f64
7812 ));
7813 },
7814 #[cfg(not(target_arch = "wasm32"))]
7816 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" | "یادگیری_گفتگو" | "تعلّم_الحوار" | "למד_דיאלוג" | "مکالمہ_سیکھو" | "apprendre_dialogue" | "dialog_lernen" | "обучить_диалог" =>
7817 {
7818 let id = self.arg_num(&args, 0, -1.0)? as i64;
7819 let text = self.arg_str(&args, 1, "");
7820 ai::dialog_learn(id, &text);
7821 return Ok(Value::Unit);
7822 },
7823 #[cfg(not(target_arch = "wasm32"))]
7825 "dialog_load" | "对话载入" | "対話読込" | "대화불러오기" | "โหลดชุดสนทนา" | "بارگذاری_مجموعه_گفتگو" | "حمّل_مجموعة_الحوار" | "טען_מערך_דיאלוג" | "مکالمہ_مجموعہ_لوڈ" | "charger_dialogue" | "dialog_laden" | "загрузить_диалог" =>
7826 {
7827 let id = self.arg_num(&args, 0, -1.0)? as i64;
7828 let path = self.arg_str(&args, 1, "");
7829 return Ok(Value::Number(ai::dialog_load(id, &path) as f64));
7830 },
7831 #[cfg(not(target_arch = "wasm32"))]
7833 "dialog_train" | "对话训练" | "対話訓練" | "대화훈련" | "ฝึกสนทนา" | "آموزش_گفتگو" | "درّب_الحوار" | "אמן_דיאלוג" | "مکالمہ_ٹریننگ" | "entraîner_dialogue" | "dialog_trainieren" | "тренировать_диалог" =>
7834 {
7835 let id = self.arg_num(&args, 0, -1.0)? as i64;
7836 let epochs = self.arg_num(&args, 1, 20.0)?.max(1.0) as usize;
7837 let lr = self.arg_num(&args, 2, 0.1)? as f32;
7838 return Ok(Value::Number(ai::dialog_train(id, epochs, lr) as f64));
7839 },
7840 #[cfg(not(target_arch = "wasm32"))]
7842 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" | "بگو" | "قل" | "אמור" | "کہو" | "dire_dialogue" | "dialog_sagen" | "сказать_диалог" =>
7843 {
7844 let id = self.arg_num(&args, 0, -1.0)? as i64;
7845 let prompt = self.arg_str(&args, 1, "");
7846 let max = self.arg_num(&args, 2, 24.0)?.max(1.0) as usize;
7847 let temp = self.arg_num(&args, 3, 0.8)? as f32;
7848 return Ok(Value::Str(ai::dialog_say(id, &prompt, max, temp)));
7849 },
7850 #[cfg(not(target_arch = "wasm32"))]
7852 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" | "ذخیره_مدل_گفتگو" | "احفظ_نموذج_الحوار" | "שמור_מודל_דיאלוג" | "مکالمہ_ماڈل_محفوظ" | "sauvegarder_dialogue" | "dialog_speichern" | "сохранить_диалог" =>
7853 {
7854 let id = self.arg_num(&args, 0, -1.0)? as i64;
7855 let path = self.arg_str(&args, 1, "model.llm");
7856 return Ok(Value::Bool(ai::dialog_save(id, &path)));
7857 },
7858 #[cfg(not(target_arch = "wasm32"))]
7860 "dialog_load_model"
7861 | "对话载模"
7862 | "対話モデル読込"
7863 | "대화모델불러오기"
7864 | "โหลดโมเดลสนทนา" | "بارگذاری_مدل_گفتگو" | "حمّل_نموذج_الحوار" | "טען_מודל_דיאלוג" | "مکالمہ_ماڈل_لوڈ" | "charger_modèle_dialogue" | "dialog_modell_laden" | "загрузить_модель_диалога" => {
7865 let path = self.arg_str(&args, 0, "model.llm");
7866 return Ok(Value::Number(ai::dialog_load_model(&path) as f64));
7867 },
7868
7869 "url_decode" | "网址解码" => {
7872 let s = self.arg_str(&args, 0, "");
7873 let bytes = s.as_bytes();
7874 let mut out = Vec::with_capacity(bytes.len());
7875 let mut i = 0;
7876 while i < bytes.len() {
7877 match bytes[i] {
7878 b'+' => {
7879 out.push(b' ');
7880 i += 1;
7881 },
7882 b'%' if i + 2 < bytes.len() => {
7883 let hex = std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or("");
7884 match u8::from_str_radix(hex, 16) {
7885 Ok(b) => {
7886 out.push(b);
7887 i += 3;
7888 },
7889 Err(_) => {
7890 out.push(bytes[i]);
7891 i += 1;
7892 },
7893 }
7894 },
7895 b => {
7896 out.push(b);
7897 i += 1;
7898 },
7899 }
7900 }
7901 return Ok(Value::Str(String::from_utf8_lossy(&out).into_owned()));
7902 },
7903 "now_unix" | "现在时间" => {
7907 return Ok(Value::Number(now_secs()));
7908 },
7909 "file_exists" | "文件存在" => {
7910 #[cfg(target_arch = "wasm32")]
7911 return Ok(Value::Bool(false));
7912 #[cfg(not(target_arch = "wasm32"))]
7913 {
7914 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7915 return Ok(Value::Bool(std::path::Path::new(&path).exists()));
7916 }
7917 },
7918 "write_file" | "เขียนไฟล์" | "نوشتن_فایل" | "اكتب_الملف" | "כתוב_קובץ" | "فائل_لکھو" => {
7919 #[cfg(target_arch = "wasm32")]
7920 return Ok(Value::Unit);
7921 #[cfg(not(target_arch = "wasm32"))]
7922 {
7923 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7924 let content = self.arg_str(&args, 1, "");
7925 if let Some(parent) = std::path::Path::new(&path).parent() {
7926 if !parent.as_os_str().is_empty() {
7927 let _ = std::fs::create_dir_all(parent);
7928 }
7929 }
7930 std::fs::write(&path, content.as_bytes())
7931 .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
7932 return Ok(Value::Unit);
7933 }
7934 },
7935 "print_file" | "พิมพ์ไฟล์" | "چاپ_فایل" | "اطبع_الملف" | "הדפס_קובץ" | "فائل_چھاپو" => {
7936 let content = self.arg_str(&args, 0, "");
7937 print!("{content}");
7938 return Ok(Value::Unit);
7939 },
7940
7941 "get_args" | "รับอาร์กิวเมนต์" | "دریافت_آرگومانها" | "اجلب_المعاملات" | "קבל_ארגומנטים" | "دلائل_حاصل_کرو" => {
7943 let v: Vec<Value> = std::env::args().map(Value::Str).collect();
7944 return Ok(Value::List(Rc::new(v)));
7945 },
7946
7947 #[cfg(not(target_arch = "wasm32"))]
7952 "list_dir" | "รายการไดเรกทอรี" | "فهرست_پوشه" | "اسرد_المجلد" | "רשום_תיקייה" | "فولڈر_فہرست" => {
7953 let path = self.arg_str(&args, 0, ".").replace('\\', "/");
7954 let mut paths: Vec<String> = Vec::new();
7955 if let Ok(rd) = std::fs::read_dir(&path) {
7956 for e in rd.flatten() {
7957 paths.push(e.path().to_string_lossy().replace('\\', "/"));
7958 }
7959 }
7960 paths.sort();
7961 let out: Vec<Value> = paths.into_iter().map(Value::Str).collect();
7962 return Ok(Value::List(Rc::new(out)));
7963 },
7964 #[cfg(not(target_arch = "wasm32"))]
7965 "is_dir" | "เป็นไดเรกทอรี" | "آیا_پوشه_است" | "هل_مجلد" | "האם_תיקייה" | "کیا_فولڈر_ہے" => {
7966 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7967 return Ok(Value::Bool(std::path::Path::new(&path).is_dir()));
7968 },
7969 #[cfg(not(target_arch = "wasm32"))]
7970 "is_file" | "เป็นไฟล์" | "آیا_فایل_است" | "هل_ملف" | "האם_קובץ" | "کیا_فائل_ہے" => {
7971 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7972 return Ok(Value::Bool(std::path::Path::new(&path).is_file()));
7973 },
7974 #[cfg(not(target_arch = "wasm32"))]
7975 "path_name" | "ชื่อไฟล์" | "نام_مسیر" | "اسم_المسار" | "שם_נתיב" | "پاتھ_نام" => {
7976 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7977 let name = std::path::Path::new(&path)
7978 .file_name()
7979 .map(|s| s.to_string_lossy().into_owned())
7980 .unwrap_or_default();
7981 return Ok(Value::Str(name));
7982 },
7983 #[cfg(not(target_arch = "wasm32"))]
7984 "path_ext" | "นามสกุลไฟล์" | "پسوند_مسیر" | "امتداد_المسار" | "סיומת_נתיב" | "پاتھ_ایکسٹینشن" => {
7985 let path = self.arg_str(&args, 0, "").replace('\\', "/");
7986 let ext = std::path::Path::new(&path)
7987 .extension()
7988 .map(|s| s.to_string_lossy().to_lowercase())
7989 .unwrap_or_default();
7990 return Ok(Value::Str(ext));
7991 },
7992 #[cfg(not(target_arch = "wasm32"))]
7993 "file_size" | "ขนาดไฟล์" | "اندازه_فایل" | "حجم_الملف" | "גודל_קובץ" | "فائل_سائز" => {
7994 let path = self.arg_str(&args, 0, "");
7995 let sz = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
7996 return Ok(Value::Number(sz as f64));
7997 },
7998 #[cfg(not(target_arch = "wasm32"))]
7999 "file_modified" | "เวลาที่แก้ไข" | "زمان_تغییر_فایل" | "وقت_تعديل_الملف" | "זמן_עדכון_קובץ" | "فائل_ترمیم_وقت" => {
8000 let path = self.arg_str(&args, 0, "");
8001 let secs = std::fs::metadata(&path)
8002 .and_then(|m| m.modified())
8003 .ok()
8004 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8005 .map(|d| d.as_secs_f64())
8006 .unwrap_or(0.0);
8007 return Ok(Value::Number(secs));
8008 },
8009 #[cfg(not(target_arch = "wasm32"))]
8010 "file_created" | "เวลาที่สร้าง" | "زمان_ایجاد_فایل" | "وقت_إنشاء_الملف" | "זמן_יצירת_קובץ" | "فائل_تخلیق_وقت" => {
8011 let path = self.arg_str(&args, 0, "");
8012 let secs = std::fs::metadata(&path)
8013 .ok()
8014 .and_then(|m| m.created().or_else(|_| m.modified()).ok())
8015 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8016 .map(|d| d.as_secs_f64())
8017 .unwrap_or(0.0);
8018 return Ok(Value::Number(secs));
8019 },
8020 #[cfg(not(target_arch = "wasm32"))]
8021 "make_dir" | "สร้างไดเรกทอรี" | "ساخت_پوشه" | "أنشئ_مجلدا" | "צור_תיקייה" | "فولڈر_بناؤ" => {
8022 let path = self.arg_str(&args, 0, "");
8023 return Ok(Value::Bool(std::fs::create_dir_all(&path).is_ok()));
8024 },
8025 "str_strip_prefix" | "ตัดคำนำหน้า" | "حذف_پیشوند" | "أزل_البادئة" | "הסר_קידומת" | "سابقہ_ہٹاؤ" => {
8027 let s = self.arg_str(&args, 0, "");
8028 let prefix = self.arg_str(&args, 1, "");
8029 return Ok(Value::Str(
8030 s.strip_prefix(&prefix).unwrap_or(&s).to_string(),
8031 ));
8032 },
8033 #[cfg(not(target_arch = "wasm32"))]
8036 "file_magic" | "มายาไฟล์" | "امضای_فایل" | "توقيع_الملف" | "חתימת_קובץ" | "فائل_میجک" => {
8037 let path = self.arg_str(&args, 0, "");
8038 let kind = match std::fs::File::open(&path) {
8039 Ok(mut f) => {
8040 use std::io::Read;
8041 let mut buf = [0u8; 4];
8042 let n = f.read(&mut buf).unwrap_or(0);
8043 if n >= 2 && buf[0] == 0x1f && buf[1] == 0x8b {
8044 "gzip"
8045 } else if n >= 4 && &buf[0..2] == b"PK" {
8046 "zip"
8047 } else {
8048 "other"
8049 }
8050 },
8051 Err(_) => "missing",
8052 };
8053 return Ok(Value::Str(kind.to_string()));
8054 },
8055 #[cfg(not(target_arch = "wasm32"))]
8057 "copy_file" | "คัดลอกไฟล์" | "کپی_فایل" | "انسخ_الملف" | "העתק_קובץ" | "فائل_کاپی" => {
8058 let src = self.arg_str(&args, 0, "");
8059 let dst = self.arg_str(&args, 1, "");
8060 if let Some(parent) = std::path::Path::new(&dst).parent() {
8061 let _ = std::fs::create_dir_all(parent);
8062 }
8063 return Ok(Value::Bool(std::fs::copy(&src, &dst).is_ok()));
8064 },
8065 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8069 "tar_gz_list" | "รายการทาร์" | "فهرست_TAR_GZ" | "اسرد_TAR_GZ" | "רשום_TAR_GZ" | "TAR_GZ_فہرست" => {
8070 let path = self.arg_str(&args, 0, "");
8071 let mut names: Vec<String> = Vec::new();
8072 if let Ok(file) = std::fs::File::open(&path) {
8073 let gz = flate2::read::GzDecoder::new(file);
8074 let mut ar = tar::Archive::new(gz);
8075 if let Ok(entries) = ar.entries() {
8076 for entry in entries.flatten() {
8077 if entry.header().entry_type().is_file() {
8078 if let Ok(p) = entry.path() {
8079 names.push(
8080 p.to_string_lossy()
8081 .trim_start_matches("./")
8082 .replace('\\', "/"),
8083 );
8084 }
8085 }
8086 }
8087 }
8088 }
8089 names.sort();
8090 names.dedup();
8091 let out: Vec<Value> = names.into_iter().map(Value::Str).collect();
8092 return Ok(Value::List(Rc::new(out)));
8093 },
8094 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8098 "tar_gz_read" | "อ่านทาร์" | "خواندن_TAR_GZ" | "اقرأ_TAR_GZ" | "קרא_TAR_GZ" | "TAR_GZ_پڑھو" => {
8099 let path = self.arg_str(&args, 0, "");
8100 let want = self.arg_str(&args, 1, "");
8101 let want = want.trim_start_matches("./").replace('\\', "/");
8102 let mut content = String::new();
8103 if let Ok(file) = std::fs::File::open(&path) {
8104 let gz = flate2::read::GzDecoder::new(file);
8105 let mut ar = tar::Archive::new(gz);
8106 if let Ok(entries) = ar.entries() {
8107 for entry in entries.flatten() {
8108 let mut entry = entry;
8109 let name = match entry.path() {
8110 Ok(p) => p
8111 .to_string_lossy()
8112 .trim_start_matches("./")
8113 .replace('\\', "/"),
8114 Err(_) => continue,
8115 };
8116 if name == want {
8117 use std::io::Read;
8118 let mut buf = Vec::new();
8119 let cap = 256 * 1024;
8120 if entry.take(cap as u64 + 1).read_to_end(&mut buf).is_ok() {
8121 let slice = if buf.len() > cap { &buf[..cap] } else { &buf[..] };
8122 content = String::from_utf8_lossy(slice).into_owned();
8123 }
8124 break;
8125 }
8126 }
8127 }
8128 }
8129 return Ok(Value::Str(content));
8130 },
8131 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8138 "totp_secret" | "โทเทนลับ" | "راز_TOTP" | "سر_TOTP" | "סוד_TOTP" | "TOTP_راز" => {
8139 let mut bytes = [0u8; 20];
8140 rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut bytes);
8141 return Ok(Value::Str(base32_encode(&bytes)));
8142 },
8143 "totp_uri" | "โทเทนยูอาร์ไอ" | "آدرس_TOTP" | "رابط_TOTP" | "כתובת_TOTP" | "TOTP_یو_آر_آئی" => {
8147 let secret = self.arg_str(&args, 0, "");
8148 let account = self.arg_str(&args, 1, "user");
8149 let issuer = self.arg_str(&args, 2, "lingfu");
8150 return Ok(Value::Str(format!(
8151 "otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}&algorithm=SHA1&digits=6&period=30"
8152 )));
8153 },
8154 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8156 "totp_verify" | "โทเทนตรวจ" | "تایید_TOTP" | "تحقق_TOTP" | "אמת_TOTP" | "TOTP_تصدیق" => {
8157 let secret = self.arg_str(&args, 0, "");
8158 let code = self.arg_str(&args, 1, "");
8159 let ok = totp_check(&secret, code.trim());
8160 return Ok(Value::Bool(ok));
8161 },
8162 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8164 "totp_now" | "โทเทนตอนนี้" | "TOTP_اکنون" | "TOTP_الآن" | "TOTP_עכשיו" | "TOTP_ابھی" => {
8165 let secret = self.arg_str(&args, 0, "");
8166 let step = (crate::runtime::now_secs() as u64) / 30;
8167 return Ok(Value::Str(
8168 totp_code(&secret, step).unwrap_or_default(),
8169 ));
8170 },
8171 #[cfg(not(target_arch = "wasm32"))]
8173 "file_hash" | "แฮชไฟล์" | "درهم_فایل" | "بصمة_الملف" | "גיבוב_קובץ" | "فائل_ہیش" => {
8174 let path = self.arg_str(&args, 0, "");
8175 match std::fs::read(&path) {
8176 Ok(bytes) => {
8177 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(&bytes))))
8178 },
8179 Err(_) => return Ok(Value::Str(String::new())),
8180 }
8181 },
8182 "hash_hex" | "แฮชสตริง" | "درهم_هگزادسیمال" | "بصمة_سداسية" | "גיבוב_הקסדצימלי" | "ہیکس_ہیش" => {
8184 let s = self.arg_str(&args, 0, "");
8185 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(
8186 s.as_bytes(),
8187 ))));
8188 },
8189 #[cfg(not(target_arch = "wasm32"))]
8191 "env_get" | "รับตัวแปรแวดล้อม" | "دریافت_متغیر_محیطی" | "اجلب_متغير_البيئة" | "קבל_משתנה_סביבה" | "ماحولیاتی_متغیر_حاصل_کرو" => {
8192 let name = self.arg_str(&args, 0, "");
8193 let dflt = self.arg_str(&args, 1, "");
8194 return Ok(Value::Str(std::env::var(&name).unwrap_or(dflt)));
8195 },
8196
8197 "to_number" | "转数字" => {
8201 let s = self.arg_str(&args, 0, "");
8202 return Ok(Value::Number(s.trim().parse().unwrap_or(0.0)));
8203 },
8204 "sha256_hex" | "SHA256哈希" => {
8209 use sha2::Digest;
8210 let s = self.arg_str(&args, 0, "");
8211 let mut h = sha2::Sha256::new();
8212 h.update(s.as_bytes());
8213 return Ok(Value::Str(hex_encode(&h.finalize())));
8214 },
8215 "hex_to_number" | "十六进制转数字" => {
8218 let s = self.arg_str(&args, 0, "");
8219 let v = u64::from_str_radix(s.trim(), 16).unwrap_or(0);
8220 return Ok(Value::Number(v as f64));
8221 },
8222 "split" | "str_split" | "แยก" | "جداسازی" | "قسّم" | "פצל" | "تقسیم_کرو" => {
8223 let s = self.arg_str(&args, 0, "");
8224 let sep = self.arg_str(&args, 1, "\n");
8225 let sep = if sep.is_empty() { "\n".into() } else { sep };
8226 let parts: Vec<Value> = s
8227 .split(sep.as_str())
8228 .map(|p| Value::Str(p.to_string()))
8229 .collect();
8230 return Ok(Value::List(Rc::new(parts)));
8231 },
8232 "trim" | "str_trim" | "ตัดช่องว่าง" | "حذف_فاصله" | "اقتطع_الفراغات" | "חתוך_רווחים" | "خالی_جگہ_کاٹو" => {
8233 let s = self.arg_str(&args, 0, "");
8234 return Ok(Value::Str(s.trim().to_string()));
8235 },
8236 "starts_with" | "str_starts_with" | "เริ่มด้วย" | "شروع_میشود_با" | "يبدأ_بـ" | "מתחיל_ב" | "شروع_ہوتا_ہے" => {
8237 let s = self.arg_str(&args, 0, "");
8238 let prefix = self.arg_str(&args, 1, "");
8239 return Ok(Value::Bool(s.starts_with(prefix.as_str())));
8240 },
8241 "ends_with" | "str_ends_with" | "ลงท้ายด้วย" | "پایان_مییابد_با" | "ينتهي_بـ" | "מסתיים_ב" | "ختم_ہوتا_ہے" => {
8242 let s = self.arg_str(&args, 0, "");
8243 let suffix = self.arg_str(&args, 1, "");
8244 return Ok(Value::Bool(s.ends_with(suffix.as_str())));
8245 },
8246 "str_replace" | "แทนสตริง" | "جایگزینی_رشته" | "استبدل_النص" | "החלף_מחרוזת" | "اسٹرنگ_تبدیل" => {
8247 let s = self.arg_str(&args, 0, "");
8248 let from = self.arg_str(&args, 1, "");
8249 let to = self.arg_str(&args, 2, "");
8250 return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
8251 },
8252 "str_find" | "หาในสตริง" | "جستجوی_رشته" | "ابحث_في_النص" | "חפש_מחרוזת" | "اسٹرنگ_تلاش" => {
8253 let s = self.arg_str(&args, 0, "");
8254 let needle = self.arg_str(&args, 1, "");
8255 let pos = s
8257 .find(needle.as_str())
8258 .map(|byte_i| s[..byte_i].chars().count() as f64)
8259 .unwrap_or(-1.0);
8260 return Ok(Value::Number(pos));
8261 },
8262 "substr" | "str_slice" | "ส่วนสตริง" | "زیررشته" | "جزء_النص" | "תת_מחרוזת" | "ذیلی_اسٹرنگ" => {
8263 let s = self.arg_str(&args, 0, "");
8264 let start = self.arg_num(&args, 1, 0.0)? as usize;
8265 let len = args
8266 .get(2)
8267 .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
8268 .unwrap_or_else(|| s.chars().count().saturating_sub(start));
8269 let chars: Vec<char> = s.chars().collect();
8270 let end = (start + len).min(chars.len());
8271 let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
8272 return Ok(Value::Str(slice));
8273 },
8274 "to_str" | "str" | "num_str" | "แปลงสตริง" | "تبدیل_به_رشته" | "حوّل_لنص" | "המר_למחרוזת" | "اسٹرنگ_میں_بدلو" => {
8275 let v = args.into_iter().next().unwrap_or(Value::Unit);
8276 return Ok(Value::Str(v.to_string()));
8277 },
8278 "str_repeat" | "ทำซ้ำสตริง" | "تکرار_رشته" | "كرّر_النص" | "חזור_על_מחרוזת" | "اسٹرنگ_دہراؤ" => {
8279 let s = self.arg_str(&args, 0, "");
8280 let n = self.arg_num(&args, 1, 1.0)? as usize;
8281 return Ok(Value::Str(s.repeat(n)));
8282 },
8283 "str_upper" => {
8284 let s = self.arg_str(&args, 0, "");
8285 return Ok(Value::Str(s.to_uppercase()));
8286 },
8287 "str_lower" => {
8288 let s = self.arg_str(&args, 0, "");
8289 return Ok(Value::Str(s.to_lowercase()));
8290 },
8291 "str_len" | "len" | "ความยาว" | "长度" | "長さ" | "길이" | "طول_رشته" | "طول_النص" | "אורך_מחרוזת" | "اسٹرنگ_لمبائی" => {
8292 match args.first() {
8293 Some(Value::Str(s)) => return Ok(Value::Number(s.chars().count() as f64)),
8294 Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
8295 _ => return Ok(Value::Number(0.0)),
8296 }
8297 },
8298
8299 "hash_str" | "แฮช" | "درهم_رشته" | "بصمة_نص" | "גיבוב_מחרוזת" | "اسٹرنگ_ہیش" => {
8301 let s = self.arg_str(&args, 0, "");
8302 let mut h: u64 = 14695981039346656037_u64;
8303 for b in s.bytes() {
8304 h ^= b as u64;
8305 h = h.wrapping_mul(1099511628211);
8306 }
8307 return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
8308 },
8309 "hash_int" | "แฮชจำนวน" | "درهم_عدد" | "بصمة_عدد" | "גיבוב_מספר" | "نمبر_ہیش" => {
8310 let s = self.arg_str(&args, 0, "");
8311 let n = self.arg_num(&args, 1, 100.0)? as u64;
8312 let mut h: u64 = 14695981039346656037_u64;
8313 for b in s.bytes() {
8314 h ^= b as u64;
8315 h = h.wrapping_mul(1099511628211);
8316 }
8317 return Ok(Value::Number((h % n.max(1)) as f64));
8318 },
8319
8320 "list_new" | "รายการใหม่" | "新建列表" | "新規リスト" | "새목록" | "فهرست_جدید" | "قائمة_جديدة" | "רשימה_חדשה" | "نئی_فہرست" | "nouvelle_liste" | "neue_liste" | "новый_список" =>
8322 {
8323 return Ok(Value::List(Rc::new(Vec::new())));
8324 },
8325 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가" | "افزودن_به_فهرست" | "أضف_للقائمة" | "הוסף_לרשימה" | "فہرست_میں_شامل_کرو" | "ajouter_liste" | "liste_anhängen" | "добавить_в_список" =>
8326 {
8327 let lst = args
8328 .first()
8329 .cloned()
8330 .unwrap_or(Value::List(Rc::new(vec![])));
8331 let val = args.get(1).cloned().unwrap_or(Value::Unit);
8332 if let Value::List(mut v) = lst {
8333 Rc::make_mut(&mut v).push(val);
8334 return Ok(Value::List(v));
8335 }
8336 return Ok(Value::List(Rc::new(vec![val])));
8337 },
8338 "list_get" | "รับรายการ" | "取元素" | "要素取得" | "요소가져오기" | "دریافت_از_فهرست" | "اجلب_من_القائمة" | "קבל_מרשימה" | "فہرست_سے_حاصل_کرو" | "obtenir_liste" | "liste_abrufen" | "получить_из_списка" =>
8339 {
8340 let i = self.arg_num(&args, 1, 0.0)? as usize;
8342 if let Some(Value::List(v)) = args.first() {
8343 return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
8344 }
8345 return Ok(Value::Str(String::new()));
8346 },
8347 "list_max" | "列表最大值" => {
8351 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8352 let default = self.arg_num(&args, 1, 0.0)?;
8353 if let Value::List(v) = lst {
8354 let mut best = default;
8355 let mut any = false;
8356 for item in v.iter() {
8357 if let Value::Number(n) = item {
8358 if !any || *n > best {
8359 best = *n;
8360 any = true;
8361 }
8362 }
8363 }
8364 return Ok(Value::Number(best));
8365 }
8366 return Ok(Value::Number(default));
8367 },
8368 "list_min" | "列表最小值" => {
8369 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8370 let default = self.arg_num(&args, 1, 0.0)?;
8371 if let Value::List(v) = lst {
8372 let mut best = default;
8373 let mut any = false;
8374 for item in v.iter() {
8375 if let Value::Number(n) = item {
8376 if !any || *n < best {
8377 best = *n;
8378 any = true;
8379 }
8380 }
8381 }
8382 return Ok(Value::Number(best));
8383 }
8384 return Ok(Value::Number(default));
8385 },
8386 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정" | "تنظیم_عنصر_فهرست" | "عيّن_عنصر_القائمة" | "קבע_איבר_רשימה" | "فہرست_سیٹ" =>
8390 {
8391 let idx = self.arg_num(&args, 1, 0.0)? as usize;
8392 let mut ai = args.into_iter();
8393 let lst = ai.next().unwrap_or(Value::List(Rc::new(vec![])));
8394 ai.next(); let val = ai.next().unwrap_or(Value::Unit);
8396 if let Value::List(mut v) = lst {
8397 if idx < v.len() {
8398 Rc::make_mut(&mut v)[idx] = val;
8399 }
8400 return Ok(Value::List(v));
8401 }
8402 return Ok(Value::List(Rc::new(vec![])));
8403 },
8404 "list_join" | "join" | "รวมรายการ" | "连接" | "連結" | "연결" | "پیوستن_فهرست" | "اربط_القائمة" | "חבר_רשימה" | "فہرست_جوڑو" =>
8405 {
8406 let lst = args
8407 .first()
8408 .cloned()
8409 .unwrap_or(Value::List(Rc::new(vec![])));
8410 let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8411 if let Value::List(v) = lst {
8412 return Ok(Value::Str(
8413 v.iter()
8414 .map(|x| x.to_string())
8415 .collect::<Vec<_>>()
8416 .join(&sep),
8417 ));
8418 }
8419 return Ok(Value::Str(String::new()));
8420 },
8421 "list_map" | "映射列表" => {
8430 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8431 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8432 if let Value::List(v) = lst {
8433 let mut out = Vec::with_capacity(v.len());
8434 for item in v.iter() {
8435 out.push(self.call_value(f.clone(), vec![item.clone()])?);
8436 }
8437 return Ok(Value::List(Rc::new(out)));
8438 }
8439 return Ok(Value::List(Rc::new(vec![])));
8440 },
8441 "list_filter" | "过滤列表" => {
8442 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8443 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8444 if let Value::List(v) = lst {
8445 let mut out = Vec::new();
8446 for item in v.iter() {
8447 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8448 out.push(item.clone());
8449 }
8450 }
8451 return Ok(Value::List(Rc::new(out)));
8452 }
8453 return Ok(Value::List(Rc::new(vec![])));
8454 },
8455 "list_find" | "查找列表" => {
8457 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8458 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8459 if let Value::List(v) = lst {
8460 for item in v.iter() {
8461 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8462 return Ok(item.clone());
8463 }
8464 }
8465 }
8466 return Ok(Value::Unit);
8467 },
8468 #[cfg(not(target_arch = "wasm32"))]
8472 "blob_f32" | "blob_i32" => {
8473 let s = self.arg_str(&args, 0, "");
8474 let is_i32 = name == "blob_i32";
8475 match decode_blob(&s) {
8476 Ok(bytes) => {
8477 let mut out = Vec::with_capacity(bytes.len() / 4);
8478 for ch in bytes.chunks_exact(4) {
8479 let arr = [ch[0], ch[1], ch[2], ch[3]];
8480 let n = if is_i32 {
8481 i32::from_le_bytes(arr) as f64
8482 } else {
8483 f32::from_le_bytes(arr) as f64
8484 };
8485 out.push(Value::Number(n));
8486 }
8487 return Ok(Value::List(Rc::new(out)));
8488 },
8489 Err(e) => {
8490 eprintln!("blob decode failed: {e}");
8491 return Ok(Value::List(Rc::new(vec![])));
8492 },
8493 }
8494 },
8495
8496 "svg_begin" | "开始SVG" | "เริ่มSVG" | "شروع_SVG" | "ابدأ_SVG" | "התחל_SVG" | "SVG_شروع" | "commencer_svg" | "svg_beginnen" | "начать_svg" => {
8503 let path = self.arg_str(&args, 0, "output.svg");
8504 let width = self.arg_num(&args, 1, 800.0)?;
8505 let height = self.arg_num(&args, 2, 600.0)?;
8506 *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
8507 return Ok(Value::Unit);
8508 },
8509
8510 "svg_rect" | "SVG矩形" | "SVGสี่เหลี่ยม" | "مستطیل_SVG" | "مستطيل_SVG" | "מלבן_SVG" | "SVG_مستطیل" | "rectangle_svg" | "svg_rechteck" | "прямоугольник_svg" => {
8511 let x = self.arg_num(&args, 0, 0.0)?;
8512 let y = self.arg_num(&args, 1, 0.0)?;
8513 let w = self.arg_num(&args, 2, 10.0)?;
8514 let h = self.arg_num(&args, 3, 10.0)?;
8515 let fill = self.arg_str(&args, 4, "#ffffff");
8516 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8517 svg.elements.push(format!(
8518 "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
8519 height=\"{h:.1}\" fill=\"{fill}\"/>"
8520 ));
8521 }
8522 return Ok(Value::Unit);
8523 },
8524
8525 "svg_circle" | "SVG圆形" | "SVGวงกลม" | "دایره_SVG" | "دائرة_SVG" | "עיגול_SVG" | "SVG_دائرہ" | "cercle_svg" | "svg_kreis" | "круг_svg" => {
8526 let cx = self.arg_num(&args, 0, 0.0)?;
8527 let cy = self.arg_num(&args, 1, 0.0)?;
8528 let r = self.arg_num(&args, 2, 5.0)?;
8529 let fill = self.arg_str(&args, 3, "#ffffff");
8530 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8531 svg.elements.push(format!(
8532 "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"
8533 ));
8534 }
8535 return Ok(Value::Unit);
8536 },
8537
8538 "svg_line" | "SVG线段" | "SVGเส้น" | "خط_SVG" | "קו_SVG" | "SVG_لکیر" | "ligne_svg" | "svg_linie" | "линия_svg" => {
8539 let x1 = self.arg_num(&args, 0, 0.0)?;
8540 let y1 = self.arg_num(&args, 1, 0.0)?;
8541 let x2 = self.arg_num(&args, 2, 0.0)?;
8542 let y2 = self.arg_num(&args, 3, 0.0)?;
8543 let stroke = self.arg_str(&args, 4, "#ffffff");
8544 let sw = self.arg_num(&args, 5, 1.0)?;
8545 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8546 svg.elements.push(format!(
8547 "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
8548 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8549 ));
8550 }
8551 return Ok(Value::Unit);
8552 },
8553
8554 "svg_polyline" | "SVG折线" | "SVGเส้นหัก" | "چندخطی_SVG" | "خط_متعدد_SVG" | "קו_שבור_SVG" | "SVG_پولی_لائن" | "polyligne_svg" | "svg_polylinie" | "ломаная_svg" => {
8555 let pts = self.arg_str(&args, 0, "");
8556 let stroke = self.arg_str(&args, 1, "#ffffff");
8557 let sw = self.arg_num(&args, 2, 1.0)?;
8558 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8559 svg.elements.push(format!(
8560 "<polyline points=\"{pts}\" fill=\"none\" \
8561 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8562 ));
8563 }
8564 return Ok(Value::Unit);
8565 },
8566
8567 "svg_text" | "SVG文本" | "SVGข้อความ" | "متن_SVG" | "نص_SVG" | "טקסט_SVG" | "SVG_متن" | "texte_svg" | "текст_svg" => {
8568 let x = self.arg_num(&args, 0, 0.0)?;
8569 let y = self.arg_num(&args, 1, 0.0)?;
8570 let text = self.arg_str(&args, 2, "");
8571 let fill = self.arg_str(&args, 3, "#ffffff");
8572 let size = self.arg_num(&args, 4, 12.0)?;
8573 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8574 let safe = text
8575 .replace('&', "&")
8576 .replace('<', "<")
8577 .replace('>', ">");
8578 svg.elements.push(format!(
8579 "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
8580 font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"
8581 ));
8582 }
8583 return Ok(Value::Unit);
8584 },
8585
8586 "svg_end" | "结束SVG" | "จบSVG" | "پایان_SVG" | "أنهِ_SVG" | "סיים_SVG" | "SVG_ختم" | "terminer_svg" | "svg_beenden" | "закончить_svg" => {
8587 {
8588 let borrow = self.svg.borrow();
8589 if let Some(svg) = borrow.as_ref() {
8590 svg.save()
8591 .map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
8592 }
8593 }
8594 *self.svg.borrow_mut() = None;
8595 return Ok(Value::Unit);
8596 },
8597
8598 "hsl_color" | "HSL颜色" | "สีHSL" | "رنگ_HSL" | "لون_HSL" | "צבע_HSL" | "HSL_رنگ" | "couleur_hsl" | "hsl_farbe" | "цвет_hsl" => {
8599 let h = self.arg_num(&args, 0, 0.0)?;
8600 let s = self.arg_num(&args, 1, 70.0)?;
8601 let l = self.arg_num(&args, 2, 50.0)?;
8602 return Ok(Value::Str(hsl_to_hex(h, s, l)));
8603 },
8604
8605 #[cfg(not(target_arch = "wasm32"))]
8611 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" | "ورودی_FFT" | "أدخل_FFT" | "הזנת_FFT" | "FFT_ان_پٹ" | "fft_entrée" | "fft_eingabe" | "fft_вход" =>
8612 {
8613 if let Some(Value::List(v)) = args.first() {
8614 let samples: Vec<f32> = v
8615 .iter()
8616 .filter_map(|x| {
8617 if let Value::Number(n) = x {
8618 Some(*n as f32)
8619 } else {
8620 None
8621 }
8622 })
8623 .collect();
8624 self.fft.borrow_mut().push_samples(&samples);
8625 }
8626 return Ok(Value::Unit);
8627 },
8628
8629 #[cfg(not(target_arch = "wasm32"))]
8631 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" | "باندهای_FFT" | "نطاقات_FFT" | "פסי_FFT" | "FFT_بینڈز" | "fft_bandes" | "fft_bänder" | "fft_полосы" =>
8632 {
8633 let n = self.arg_num(&args, 0, 32.0)? as usize;
8634 let bands = self.fft.borrow().freq_bands(n);
8635 *self.fft_bands_cache.borrow_mut() = bands.clone();
8636 return Ok(Value::List(Rc::new(
8637 bands.into_iter().map(|v| Value::Number(v as f64)).collect(),
8638 )));
8639 },
8640
8641 #[cfg(not(target_arch = "wasm32"))]
8643 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" | "ضرب_FFT" | "نبضة_FFT" | "פעימת_FFT" | "FFT_دھڑکن" | "fft_battement" | "fft_takt" | "fft_удар" =>
8644 {
8645 return Ok(Value::Bool(self.fft.borrow().is_beat()));
8646 },
8647
8648 #[cfg(not(target_arch = "wasm32"))]
8650 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" | "نسبت_ضرب_FFT" | "نسبة_نبضة_FFT" | "יחס_פעימת_FFT" | "FFT_بیٹ_تناسب" =>
8651 {
8652 return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
8653 },
8654
8655 #[cfg(not(target_arch = "wasm32"))]
8657 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" | "RMS_صدا" | "جذر_متوسط_مربع_FFT" | "RMS_של_FFT" | "FFT_RMS" => {
8658 return Ok(Value::Number(self.fft.borrow().rms() as f64));
8659 },
8660
8661 #[cfg(not(target_arch = "wasm32"))]
8663 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" | "فرکانس_غالب" | "التردد_السائد" | "תדר_דומיננטי" | "غالب_فریکوئنسی" | "fft_fréquence_dominante" | "fft_dominante_frequenz" | "fft_доминирующая_частота" =>
8664 {
8665 return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
8666 },
8667
8668 #[cfg(target_arch = "wasm32")]
8670 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" | "ورودی_FFT" | "أدخل_FFT" | "הזנת_FFT" | "FFT_ان_پٹ" | "fft_entrée" | "fft_eingabe" | "fft_вход" =>
8671 {
8672 return Ok(Value::Unit);
8673 },
8674 #[cfg(target_arch = "wasm32")]
8675 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" | "باندهای_FFT" | "نطاقات_FFT" | "פסי_FFT" | "FFT_بینڈز" | "fft_bandes" | "fft_bänder" | "fft_полосы" =>
8676 {
8677 let n = self.arg_num(&args, 0, 32.0)? as usize;
8678 return Ok(Value::List(vec![Value::Number(0.0); n].into()));
8679 },
8680 #[cfg(target_arch = "wasm32")]
8681 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" | "ضرب_FFT" | "نبضة_FFT" | "פעימת_FFT" | "FFT_دھڑکن" | "fft_battement" | "fft_takt" | "fft_удар" =>
8682 {
8683 return Ok(Value::Bool(false));
8684 },
8685 #[cfg(target_arch = "wasm32")]
8686 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" | "نسبت_ضرب_FFT" | "نسبة_نبضة_FFT" | "יחס_פעימת_FFT" | "FFT_بیٹ_تناسب" =>
8687 {
8688 return Ok(Value::Number(1.0));
8689 },
8690 #[cfg(target_arch = "wasm32")]
8691 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" | "RMS_صدا" | "جذر_متوسط_مربع_FFT" | "RMS_של_FFT" | "FFT_RMS" => {
8692 return Ok(Value::Number(0.0));
8693 },
8694 #[cfg(target_arch = "wasm32")]
8695 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" | "فرکانس_غالب" | "التردد_السائد" | "תדר_דומיננטי" | "غالب_فریکوئنسی" | "fft_fréquence_dominante" | "fft_dominante_frequenz" | "fft_доминирующая_частота" =>
8696 {
8697 return Ok(Value::Number(0.0));
8698 },
8699
8700 "tex_checkerboard" | "ลายตารางหมากรุก" | "بافت_شطرنجی" | "نسيج_رقعة_الشطرنج" | "מרקם_שחמט" | "شطرنج_ٹیکسچر" => {
8708 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8709 let tiles = self.arg_num(&args, 4, 8.0)? as u32;
8710 let (r1, g1, b1) = (
8711 self.arg_num(&args, 5, 255.)? as u32,
8712 self.arg_num(&args, 6, 255.)? as u32,
8713 self.arg_num(&args, 7, 255.)? as u32,
8714 );
8715 let (r2, g2, b2) = (
8716 self.arg_num(&args, 8, 0.)? as u32,
8717 self.arg_num(&args, 9, 0.)? as u32,
8718 self.arg_num(&args, 10, 0.)? as u32,
8719 );
8720 let c1 = (r1 << 16) | (g1 << 8) | b1;
8721 let c2 = (r2 << 16) | (g2 << 8) | b2;
8722 let mut gfx = self.gfx.borrow_mut();
8723 let (bw, bh) = (gfx.width, gfx.height);
8724 for row in 0..th {
8725 for col in 0..tw {
8726 let cx = col as u32 * tiles / tw as u32;
8727 let cy = row as u32 * tiles / th as u32;
8728 let (dx, dy) = (tx + col, ty + row);
8729 if dx < bw && dy < bh {
8730 gfx.buffer[dy * bw + dx] = if (cx + cy) % 2 == 0 { c1 } else { c2 };
8731 }
8732 }
8733 }
8734 return Ok(Value::Unit);
8735 },
8736
8737 "tex_gradient" | "ลายไล่สี" | "بافت_گرادیان" | "نسيج_متدرج" | "מרקם_גרדיאנט" | "گریڈینٹ_ٹیکسچر" => {
8739 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8740 let angle = self.arg_num(&args, 4, 0.0)? as f32;
8741 let (r1, g1, b1) = (
8742 self.arg_num(&args, 5, 0.)? as f32 / 255.,
8743 self.arg_num(&args, 6, 0.)? as f32 / 255.,
8744 self.arg_num(&args, 7, 0.)? as f32 / 255.,
8745 );
8746 let (r2, g2, b2) = (
8747 self.arg_num(&args, 8, 255.)? as f32 / 255.,
8748 self.arg_num(&args, 9, 255.)? as f32 / 255.,
8749 self.arg_num(&args, 10, 255.)? as f32 / 255.,
8750 );
8751 let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
8752 let mut gfx = self.gfx.borrow_mut();
8753 let (bw, bh) = (gfx.width, gfx.height);
8754 for row in 0..th {
8755 for col in 0..tw {
8756 let nx = col as f32 / tw as f32 - 0.5;
8757 let ny = row as f32 / th as f32 - 0.5;
8758 let t = ((nx * ca + ny * sa + 0.707) / 1.414).clamp(0., 1.);
8759 let (dx, dy) = (tx + col, ty + row);
8760 if dx < bw && dy < bh {
8761 gfx.buffer[dy * bw + dx] =
8762 tex_rgb(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
8763 }
8764 }
8765 }
8766 return Ok(Value::Unit);
8767 },
8768
8769 "tex_noise" | "ลายนอยส์" | "بافت_نویز" | "نسيج_ضجيج" | "מרקם_רעש" | "نوائز_ٹیکسچر" => {
8771 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8772 let scale = self.arg_num(&args, 4, 4.0)? as f32;
8773 let octaves = self.arg_num(&args, 5, 4.0)? as u32;
8774 let seed = self.arg_num(&args, 6, 0.0)? as u32;
8775 let palette = self.arg_str(&args, 7, "rainbow");
8776 let mut gfx = self.gfx.borrow_mut();
8777 let (bw, bh) = (gfx.width, gfx.height);
8778 for row in 0..th {
8779 for col in 0..tw {
8780 let v = tex_fbm(
8781 col as f32 * scale / tw as f32,
8782 row as f32 * scale / th as f32,
8783 octaves,
8784 seed,
8785 );
8786 let [r, g, b] = tex_palette(&palette, v);
8787 let (dx, dy) = (tx + col, ty + row);
8788 if dx < bw && dy < bh {
8789 gfx.buffer[dy * bw + dx] = tex_rgb(r, g, b);
8790 }
8791 }
8792 }
8793 return Ok(Value::Unit);
8794 },
8795
8796 "tex_freq_map" | "ลายความถี่" | "نقشه_فرکانس_بافت" | "خريطة_تردد_النسيج" | "מפת_תדר_מרקם" | "فریکوئنسی_میپ_ٹیکسچر" => {
8799 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8800 let time = self.arg_num(&args, 4, 0.0)? as f32;
8801 let speed = self.arg_num(&args, 5, 0.3)? as f32;
8802 let palette = self.arg_str(&args, 6, "rainbow");
8803 let bands: Vec<f32> = {
8804 let c = self.fft_bands_cache.borrow();
8805 if c.is_empty() {
8806 vec![0.0; 32]
8807 } else {
8808 c.clone()
8809 }
8810 };
8811 let n = bands.len().max(1);
8812 let mut gfx = self.gfx.borrow_mut();
8813 let (bw, bh) = (gfx.width, gfx.height);
8814 for row in 0..th {
8815 for col in 0..tw {
8816 let band_idx = (col * n / tw.max(1)).min(n - 1);
8817 let mag = bands[band_idx].clamp(0., 1.);
8818 let fill_y = (mag * th as f32) as usize;
8819 if row >= th.saturating_sub(fill_y) {
8820 let t = (col as f32 / tw as f32 + time * speed) % 1.0;
8821 let [r, g, b] = tex_palette(&palette, t);
8822 let bright = mag * (1.0 - row as f32 / th as f32 * 0.5);
8823 let (dx, dy) = (tx + col, ty + row);
8824 if dx < bw && dy < bh {
8825 gfx.buffer[dy * bw + dx] =
8826 tex_rgb(r * bright, g * bright, b * bright);
8827 }
8828 }
8829 }
8830 }
8831 return Ok(Value::Unit);
8832 },
8833
8834 "tex_spiral" | "ลายเกลียวหมุน" | "بافت_مارپیچ" | "نسيج_حلزوني" | "מרקם_ספירלה" | "سرپیچ_ٹیکسچر" => {
8836 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8837 let freq = self.arg_num(&args, 4, 5.0)? as f32;
8838 let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
8839 let time = self.arg_num(&args, 6, 0.0)? as f32;
8840 let palette = self.arg_str(&args, 7, "rainbow");
8841 let mut gfx = self.gfx.borrow_mut();
8842 let (bw, bh) = (gfx.width, gfx.height);
8843 for row in 0..th {
8844 for col in 0..tw {
8845 let nx = col as f32 / tw as f32 - 0.5;
8846 let ny = row as f32 / th as f32 - 0.5;
8847 let r = (nx * nx + ny * ny).sqrt();
8848 let theta = ny.atan2(nx);
8849 let t = ((r * freq - theta / std::f32::consts::TAU + time * 0.5) * n_bands
8850 % 1.0)
8851 .abs();
8852 let [cr, cg, cb] = tex_palette(&palette, t);
8853 let (dx, dy) = (tx + col, ty + row);
8854 if dx < bw && dy < bh {
8855 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8856 }
8857 }
8858 }
8859 return Ok(Value::Unit);
8860 },
8861
8862 "tex_ripple" | "ลายระลอก" | "بافت_موج" | "نسيج_تموج" | "מרקם_אדווה" | "ریپل_ٹیکسچر" => {
8864 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8865 let freq = self.arg_num(&args, 4, 10.0)? as f32;
8866 let rcx = self.arg_num(&args, 5, 0.5)? as f32;
8867 let rcy = self.arg_num(&args, 6, 0.5)? as f32;
8868 let time = self.arg_num(&args, 7, 0.0)? as f32;
8869 let palette = self.arg_str(&args, 8, "ocean");
8870 let mut gfx = self.gfx.borrow_mut();
8871 let (bw, bh) = (gfx.width, gfx.height);
8872 for row in 0..th {
8873 for col in 0..tw {
8874 let nx = col as f32 / tw as f32 - rcx;
8875 let ny = row as f32 / th as f32 - rcy;
8876 let r = (nx * nx + ny * ny).sqrt();
8877 let t = ((r * freq - time) % 1.0).abs();
8878 let [cr, cg, cb] = tex_palette(&palette, t);
8879 let (dx, dy) = (tx + col, ty + row);
8880 if dx < bw && dy < bh {
8881 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8882 }
8883 }
8884 }
8885 return Ok(Value::Unit);
8886 },
8887
8888 "tex_mandelbrot" | "ลายแมนเดลบรอต" | "بافت_ماندلبرو" | "نسيج_مانديلبروت" | "מרקם_מנדלברוט" | "مینڈل_بروٹ_ٹیکسچر" => {
8890 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8891 let zoom = self.arg_num(&args, 4, 1.0)?;
8892 let mcx = self.arg_num(&args, 5, -0.5)?;
8893 let mcy = self.arg_num(&args, 6, 0.0)?;
8894 let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
8895 let palette = self.arg_str(&args, 8, "psychedelic");
8896 let mut gfx = self.gfx.borrow_mut();
8897 let (bw, bh) = (gfx.width, gfx.height);
8898 for row in 0..th {
8899 for col in 0..tw {
8900 let zx0 = (col as f64 / tw as f64 - 0.5) / zoom + mcx;
8901 let zy0 = (row as f64 / th as f64 - 0.5) / zoom + mcy;
8902 let mut x = 0.0f64;
8903 let mut y = 0.0f64;
8904 let mut i = 0u32;
8905 while i < max_iter && x * x + y * y < 4.0 {
8906 let t = x * x - y * y + zx0;
8907 y = 2.0 * x * y + zy0;
8908 x = t;
8909 i += 1;
8910 }
8911 let t = if i == max_iter {
8912 0.0f32
8913 } else {
8914 (i as f32
8915 - (x as f32 * x as f32 + y as f32 * y as f32).ln().ln()
8916 / 2.0f32.ln())
8917 / max_iter as f32
8918 };
8919 let [cr, cg, cb] = tex_palette(&palette, t.clamp(0., 1.));
8920 let (dx, dy) = (tx + col, ty + row);
8921 if dx < bw && dy < bh {
8922 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8923 }
8924 }
8925 }
8926 return Ok(Value::Unit);
8927 },
8928
8929 "tex_julia" | "ลายจูเลีย" | "بافت_ژولیا" | "نسيج_جوليا" | "מרקם_ג'וליה" | "جولیا_ٹیکسچر" => {
8931 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8932 let c_re = self.arg_num(&args, 4, -0.7)?;
8933 let c_im = self.arg_num(&args, 5, 0.27)?;
8934 let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
8935 let palette = self.arg_str(&args, 7, "neon");
8936 let mut gfx = self.gfx.borrow_mut();
8937 let (bw, bh) = (gfx.width, gfx.height);
8938 for row in 0..th {
8939 for col in 0..tw {
8940 let mut zx = (col as f64 / tw as f64 - 0.5) * 3.5;
8941 let mut zy = (row as f64 / th as f64 - 0.5) * 3.5;
8942 let mut i = 0u32;
8943 while i < max_iter && zx * zx + zy * zy < 4.0 {
8944 let t = zx * zx - zy * zy + c_re;
8945 zy = 2.0 * zx * zy + c_im;
8946 zx = t;
8947 i += 1;
8948 }
8949 let t = i as f32 / max_iter as f32;
8950 let [cr, cg, cb] = tex_palette(&palette, t);
8951 let (dx, dy) = (tx + col, ty + row);
8952 if dx < bw && dy < bh {
8953 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8954 }
8955 }
8956 }
8957 return Ok(Value::Unit);
8958 },
8959
8960 "tex_voronoi" | "ลายโวโรนอย" | "بافت_ورونوی" | "نسيج_فورونوي" | "מרקם_וורונוי" | "ورونوئی_ٹیکسچر" => {
8962 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8963 let cells = self.arg_num(&args, 4, 16.0)? as u32;
8964 let seed = self.arg_num(&args, 5, 42.0)? as u32;
8965 let palette = self.arg_str(&args, 6, "rainbow");
8966 let pts: Vec<[f32; 2]> = (0..cells)
8967 .map(|i| {
8968 [
8969 tex_hash(i as i32, 0, seed),
8970 tex_hash(i as i32, 1, seed + 999),
8971 ]
8972 })
8973 .collect();
8974 let mut gfx = self.gfx.borrow_mut();
8975 let (bw, bh) = (gfx.width, gfx.height);
8976 for row in 0..th {
8977 for col in 0..tw {
8978 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
8979 let (min_d, nearest) = pts.iter().enumerate().fold(
8980 (f32::MAX, 0usize),
8981 |(d, idx), (i, &[cx, cy])| {
8982 let dd = (fx - cx).powi(2) + (fy - cy).powi(2);
8983 if dd < d {
8984 (dd, i)
8985 } else {
8986 (d, idx)
8987 }
8988 },
8989 );
8990 let t = (nearest as f32 / cells as f32 + min_d * 4.0) % 1.0;
8991 let [cr, cg, cb] = tex_palette(&palette, t);
8992 let (dx, dy) = (tx + col, ty + row);
8993 if dx < bw && dy < bh {
8994 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8995 }
8996 }
8997 }
8998 return Ok(Value::Unit);
8999 },
9000
9001 "tex_halftone" | "ลายฮาล์ฟโทน" | "بافت_نیمتن" | "نسيج_نصفي" | "מרקם_חצי_גוון" | "ہاف_ٹون_ٹیکسچر" => {
9003 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9004 let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
9005 let time = self.arg_num(&args, 5, 0.0)? as f32;
9006 let palette = self.arg_str(&args, 6, "rainbow");
9007 let mut gfx = self.gfx.borrow_mut();
9008 let (bw, bh) = (gfx.width, gfx.height);
9009 for row in 0..th {
9010 for col in 0..tw {
9011 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
9012 let gx = (fx / dot_size).floor();
9013 let gy = (fy / dot_size).floor();
9014 let lx = (fx / dot_size - gx - 0.5) * 2.0;
9015 let ly = (fy / dot_size - gy - 0.5) * 2.0;
9016 let r = (lx * lx + ly * ly).sqrt();
9017 let t = (gx / (1.0 / dot_size) + time * 0.1) % 1.0;
9018 let a = if r < 0.7 {
9019 ((0.7 - r) / 0.7).clamp(0., 1.)
9020 } else {
9021 0.0
9022 };
9023 if a > 0.0 {
9024 let [cr, cg, cb] = tex_palette(&palette, t);
9025 let (dx, dy) = (tx + col, ty + row);
9026 if dx < bw && dy < bh {
9027 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9028 }
9029 }
9030 }
9031 }
9032 return Ok(Value::Unit);
9033 },
9034
9035 "set_shade_mode" | "设置着色" | "シェード設定" | "셰이드모드" | "ตั้งการแรเงา" | "تنظیم_حالت_سایهپردازی" | "عيّن_نمط_التظليل" | "קבע_מצב_הצללה" | "شیڈ_موڈ_مقرر_کرو" | "définir_mode_ombrage" | "schattierungsmodus_setzen" | "задать_режим_затенения" =>
9040 {
9041 let m = self.arg_num(&args, 0, 2.0)? as u8;
9042 self.gfx.borrow_mut().shade_mode = m;
9043 return Ok(Value::Unit);
9044 },
9045 "set_cel_bands" | "设置色阶" | "セル段数" | "셀밴드" | "ตั้งระดับสี" | "تنظیم_باندهای_سل" | "عيّن_نطاقات_التظليل" | "קבע_רצועות_הצללה" | "سیل_بینڈز_مقرر_کرو" | "définir_bandes_cel" | "cel_bänder_setzen" | "задать_полосы_cel" =>
9047 {
9048 let n = (self.arg_num(&args, 0, 4.0)? as u32).max(2);
9049 self.gfx.borrow_mut().shade.bands = n;
9050 return Ok(Value::Unit);
9051 },
9052 "set_shadow_color" | "设置阴影色" | "影の色" | "그림자색" | "ตั้งสีเงา" | "تنظیم_رنگ_سایه" | "عيّن_لون_الظل" | "קבע_צבע_צל" | "سایہ_رنگ_مقرر_کرو" | "définir_couleur_ombre" | "schattenfarbe_setzen" | "задать_цвет_тени" =>
9054 {
9055 let r = self.arg_num(&args, 0, 26.)? as f32 / 255.0;
9056 let g = self.arg_num(&args, 1, 33.)? as f32 / 255.0;
9057 let b = self.arg_num(&args, 2, 77.)? as f32 / 255.0;
9058 self.gfx.borrow_mut().shade.shadow = [r, g, b];
9059 return Ok(Value::Unit);
9060 },
9061 #[cfg(not(target_arch = "wasm32"))]
9067 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" | "درهم_رمزنگاری" | "بصمة_تشفير" | "גיבוב_הצפנה" | "خفیہ_ہیش" | "hachage_crypto" | "krypto_hash" | "крипто_хеш" =>
9068 {
9069 let s = self.arg_str(&args, 0, "");
9070 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9071 s.as_bytes(),
9072 ))));
9073 },
9074 #[cfg(target_arch = "wasm32")]
9075 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" | "درهم_رمزنگاری" | "بصمة_تشفير" | "גיבוב_הצפנה" | "خفیہ_ہیش" | "hachage_crypto" | "krypto_hash" | "крипто_хеш" =>
9076 {
9077 let s = self.arg_str(&args, 0, "");
9078 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9079 s.as_bytes(),
9080 ))));
9081 },
9082 #[cfg(not(target_arch = "wasm32"))]
9084 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" | "نقاط_گره" | "نقاط_العقدة" | "נקודות_קשר" | "گرہ_پوائنٹس" | "points_nœud" | "knotenpunkte" | "точки_узла" => {
9085 let s = self.arg_str(&args, 0, "");
9086 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9087 let mut out = Vec::with_capacity(shape.points.len() * 3);
9088 for p in &shape.points {
9089 out.push(Value::Number(p[0] as f64));
9090 out.push(Value::Number(p[1] as f64));
9091 out.push(Value::Number(p[2] as f64));
9092 }
9093 return Ok(Value::List(Rc::new(out)));
9094 },
9095 #[cfg(target_arch = "wasm32")]
9096 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" | "نقاط_گره" | "نقاط_العقدة" | "נקודות_קשר" | "گرہ_پوائنٹس" | "points_nœud" | "knotenpunkte" | "точки_узла" => {
9097 let s = self.arg_str(&args, 0, "");
9098 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9099 let mut out = Vec::with_capacity(shape.points.len() * 3);
9100 for p in &shape.points {
9101 out.push(Value::Number(p[0] as f64));
9102 out.push(Value::Number(p[1] as f64));
9103 out.push(Value::Number(p[2] as f64));
9104 }
9105 return Ok(Value::List(out.into()));
9106 },
9107 #[cfg(not(target_arch = "wasm32"))]
9108 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" | "برچسب_گره" | "تسمية_العقدة" | "תווית_קשר" | "گرہ_لیبل" | "étiquette_nœud" | "knotenbezeichnung" | "метка_узла" =>
9109 {
9110 let s = self.arg_str(&args, 0, "");
9111 return Ok(Value::Str(
9112 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9113 ));
9114 },
9115 #[cfg(target_arch = "wasm32")]
9116 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" | "برچسب_گره" | "تسمية_العقدة" | "תווית_קשר" | "گرہ_لیبل" | "étiquette_nœud" | "knotenbezeichnung" | "метка_узла" =>
9117 {
9118 let s = self.arg_str(&args, 0, "");
9119 return Ok(Value::Str(
9120 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9121 ));
9122 },
9123 #[cfg(not(target_arch = "wasm32"))]
9125 "knot_keygen" | "hybrid_keygen" | "สร้างกุญแจปม" | "生成密钥" | "鍵生成" | "키생성" | "تولید_کلید_گره" | "توليد_مفتاح_العقدة" | "יצירת_מפתח_קשר" | "گرہ_کلید_تخلیق" | "génération_clé_nœud" | "knotenschlüsselerzeugung" | "генерация_ключа_узла" =>
9126 {
9127 self.crypto_ids.push(ling_crypto::KnotIdentity::generate());
9128 return Ok(Value::Number((self.crypto_ids.len() - 1) as f64));
9129 },
9130 #[cfg(not(target_arch = "wasm32"))]
9131 "knot_public" | "hybrid_public" | "กุญแจสาธารณะปม" | "公钥" | "公開鍵" | "공개키" | "کلید_عمومی_گره" | "مفتاح_العقدة_العام" | "מפתח_ציבורי_קשר" | "گرہ_عوامی_کلید" | "clé_publique_nœud" | "knoten_öffentlicher_schlüssel" | "публичный_ключ_узла" =>
9132 {
9133 let h = self.arg_num(&args, 0, 0.0)? as usize;
9134 let pk = self
9135 .crypto_ids
9136 .get(h)
9137 .map(|id| hex_encode(id.public_key()))
9138 .unwrap_or_default();
9139 return Ok(Value::Str(pk));
9140 },
9141 #[cfg(not(target_arch = "wasm32"))]
9143 "knot_encapsulate"
9144 | "hybrid_encapsulate"
9145 | "ห่อกุญแจปม"
9146 | "封装密钥"
9147 | "カプセル化"
9148 | "캡슐화" | "کپسولهسازی_گره" | "تغليف_مفتاح_العقدة" | "עטיפת_קשר" | "گرہ_احاطہ" | "encapsuler_nœud" | "knoten_kapseln" | "инкапсулировать_узел" => {
9149 let pk = hex_decode(&self.arg_str(&args, 0, ""));
9150 match ling_crypto::geo::knot_encapsulate(&pk) {
9151 Ok((ct, ss)) => {
9152 return Ok(Value::List(Rc::new(vec![
9153 Value::Str(hex_encode(&ct)),
9154 Value::Str(hex_encode(&ss)),
9155 ])))
9156 },
9157 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9158 }
9159 },
9160 #[cfg(not(target_arch = "wasm32"))]
9162 "knot_decapsulate"
9163 | "hybrid_decapsulate"
9164 | "แกะกุญแจปม"
9165 | "解封装密钥"
9166 | "カプセル解除"
9167 | "캡슐해제" | "بازکردن_کپسوله_گره" | "فك_تغليف_مفتاح_العقدة" | "פתיחת_עטיפת_קשר" | "گرہ_احاطہ_کھولو" | "décapsuler_nœud" | "knoten_entkapseln" | "декапсулировать_узел" => {
9168 let h = self.arg_num(&args, 0, 0.0)? as usize;
9169 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9170 let ss = self
9171 .crypto_ids
9172 .get(h)
9173 .and_then(|id| id.decapsulate(&ct).ok())
9174 .map(|s| hex_encode(&s))
9175 .unwrap_or_default();
9176 return Ok(Value::Str(ss));
9177 },
9178 #[cfg(not(target_arch = "wasm32"))]
9180 "crypto_seal" | "ผนึก" | "封印" | "封印する" | "봉인" | "مهر_رمزنگاری" | "ختم_تشفير" | "חתימת_הצפנה" | "خفیہ_مہر" | "sceller_crypto" | "krypto_versiegeln" | "запечатать_крипто" => {
9181 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9182 let pt = self.arg_str(&args, 1, "");
9183 match ling_crypto::geo::holo_seal(key, pt.as_bytes()) {
9184 Ok(ct) => return Ok(Value::Str(hex_encode(&ct))),
9185 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9186 }
9187 },
9188 #[cfg(not(target_arch = "wasm32"))]
9189 "crypto_open" | "เปิดผนึก" | "解封" | "封印解除" | "봉인해제" | "بازکردن_مهر" | "فتح_الختم" | "פתיחת_חתימה" | "مہر_کھولو" | "ouvrir_crypto" | "krypto_öffnen" | "открыть_крипто" =>
9190 {
9191 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9192 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9193 match ling_crypto::geo::holo_open(key, &ct) {
9194 Ok(pt) => return Ok(Value::Str(String::from_utf8_lossy(&pt).into_owned())),
9195 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9196 }
9197 },
9198 #[cfg(not(target_arch = "wasm32"))]
9200 "holo_points" | "จุดโฮโลแกรม" | "全息点" | "ホログラム点" | "홀로그램점" | "نقاط_هولوگرام" | "نقاط_الهولوغرام" | "נקודות_הולוגרמה" | "ہولوگرام_پوائنٹس" | "points_holo" | "holo_punkte" | "точки_голо" =>
9201 {
9202 let s = self.arg_str(&args, 0, "");
9203 let frags = ling_crypto::geo::scatter(s.as_bytes());
9204 let mut out = Vec::with_capacity(frags.len() * 4);
9205 for f in &frags {
9206 for c in f.coord {
9207 out.push(Value::Number(c as f64));
9208 }
9209 }
9210 return Ok(Value::List(Rc::new(out)));
9211 },
9212 #[cfg(not(target_arch = "wasm32"))]
9213 "holo_fragment_count"
9214 | "จำนวนชิ้นโฮโลแกรม"
9215 | "全息碎片数"
9216 | "ホログラム断片数"
9217 | "홀로그램조각수" | "تعداد_قطعات_هولوگرام" | "عدد_شظايا_الهولوغرام" | "מספר_שברי_הולוגרמה" | "ہولوگرام_ٹکڑے_تعداد" | "nombre_fragments_holo" | "holo_fragmentanzahl" | "число_фрагментов_голо" => {
9218 let s = self.arg_str(&args, 0, "");
9219 return Ok(Value::Number(
9220 ling_crypto::geo::scatter(s.as_bytes()).len() as f64
9221 ));
9222 },
9223 #[cfg(not(target_arch = "wasm32"))]
9226 "shake_hex" | "SHAKE哈希" => {
9227 let s = self.arg_str(&args, 0, "");
9228 let len = self.arg_num(&args, 1, 32.0)?.max(0.0) as usize;
9229 return Ok(Value::Str(hex_encode(&ling_crypto::Shake256::hash(s.as_bytes(), len))));
9230 },
9231 #[cfg(not(target_arch = "wasm32"))]
9233 "ed25519_keygen"
9234 | "생성서명키"
9235 | "สร้างกุญแจลายเซ็น"
9236 | "生成签名密钥"
9237 | "署名鍵生成"
9238 | "تولید_کلید_امضا"
9239 | "توليد_مفتاح_التوقيع"
9240 | "יצירת_מפתח_חתימה"
9241 | "دستخط_کلید_تخلیق"
9242 | "génération_clé_signature"
9243 | "signaturschlüsselerzeugung"
9244 | "генерация_ключа_подписи" => {
9245 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::generate());
9246 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9247 },
9248 #[cfg(not(target_arch = "wasm32"))]
9253 "ed25519_keygen_from_seed"
9254 | "씨앗에서생성서명키"
9255 | "สร้างกุญแจลายเซ็นจากเมล็ด"
9256 | "从种子生成签名密钥"
9257 | "シードから署名鍵生成"
9258 | "تولید_کلید_امضا_از_دانه"
9259 | "توليد_مفتاح_التوقيع_من_البذرة"
9260 | "יצירת_מפתח_חתימה_מזרע"
9261 | "بیج_سے_دستخط_کلید_تخلیق"
9262 | "génération_clé_signature_depuis_graine"
9263 | "signaturschlüsselerzeugung_aus_saat"
9264 | "генерация_ключа_подписи_из_семени" => {
9265 let seed = hex_to_32(&self.arg_str(&args, 0, ""));
9266 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::from_seed(seed));
9267 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9268 },
9269 #[cfg(not(target_arch = "wasm32"))]
9270 "ed25519_public"
9271 | "서명공개키"
9272 | "กุญแจสาธารณะลายเซ็น"
9273 | "签名公钥"
9274 | "署名公開鍵"
9275 | "کلید_عمومی_امضا"
9276 | "مفتاح_التوقيع_العام"
9277 | "מפתח_חתימה_ציבורי"
9278 | "دستخط_عوامی_کلید"
9279 | "clé_publique_signature"
9280 | "signatur_öffentlicher_schlüssel"
9281 | "публичный_ключ_подписи" => {
9282 let h = self.arg_num(&args, 0, 0.0)? as usize;
9283 let pk = self
9284 .ed25519_ids
9285 .get(h)
9286 .map(|kp| hex_encode(&kp.public_key()))
9287 .unwrap_or_default();
9288 return Ok(Value::Str(pk));
9289 },
9290 #[cfg(not(target_arch = "wasm32"))]
9292 "ed25519_sign"
9293 | "서명하다"
9294 | "เซ็นชื่อ"
9295 | "签名"
9296 | "署名する"
9297 | "امضا_کردن"
9298 | "توقيع"
9299 | "לחתום"
9300 | "دستخط_کریں"
9301 | "signer"
9302 | "signieren"
9303 | "подписать" => {
9304 let h = self.arg_num(&args, 0, 0.0)? as usize;
9305 let msg = self.arg_str(&args, 1, "");
9306 let sig = self
9307 .ed25519_ids
9308 .get(h)
9309 .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
9310 .unwrap_or_default();
9311 return Ok(Value::Str(sig));
9312 },
9313 #[cfg(not(target_arch = "wasm32"))]
9315 "ed25519_verify"
9316 | "서명확인"
9317 | "ยืนยันลายเซ็น"
9318 | "验证签名"
9319 | "署名検証"
9320 | "تایید_امضا"
9321 | "التحقق_من_التوقيع"
9322 | "אימות_חתימה"
9323 | "دستخط_تصدیق"
9324 | "vérifier_signature"
9325 | "signatur_verifizieren"
9326 | "проверить_подпись" => {
9327 let pk_hex = self.arg_str(&args, 0, "");
9328 let msg = self.arg_str(&args, 1, "");
9329 let sig_hex = self.arg_str(&args, 2, "");
9330 let pk_bytes = hex_decode(&pk_hex);
9331 let sig_bytes = hex_decode(&sig_hex);
9332 let ok = (|| {
9333 let pk: [u8; 32] = pk_bytes.try_into().ok()?;
9334 let sig: [u8; 64] = sig_bytes.try_into().ok()?;
9335 Some(ling_crypto::Ed25519Keypair::verify(&pk, msg.as_bytes(), &sig).is_ok())
9336 })()
9337 .unwrap_or(false);
9338 return Ok(Value::Bool(ok));
9339 },
9340 #[cfg(not(target_arch = "wasm32"))]
9347 "mldsa_keygen"
9348 | "양자서명키생성"
9349 | "สร้างกุญแจลายเซ็นควอนตัม"
9350 | "生成量子签名密钥"
9351 | "量子署名鍵生成"
9352 | "تولید_کلید_امضای_کوانتومی"
9353 | "توليد_مفتاح_التوقيع_الكمي"
9354 | "יצירת_מפתח_חתימה_קוונטי"
9355 | "کوانٹم_دستخط_کلید_تخلیق"
9356 | "génération_clé_signature_quantique"
9357 | "quantensignaturschlüsselerzeugung"
9358 | "генерация_ключа_квантовой_подписи" => {
9359 self.mldsa_ids.push(ling_crypto::MlDsa65Keypair::generate());
9360 return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
9361 },
9362 #[cfg(not(target_arch = "wasm32"))]
9365 "mldsa_keygen_from_seed"
9366 | "씨앗에서양자서명키생성"
9367 | "สร้างกุญแจลายเซ็นควอนตัมจากเมล็ด"
9368 | "从种子生成量子签名密钥"
9369 | "シードから量子署名鍵生成"
9370 | "تولید_کلید_امضای_کوانتومی_از_دانه"
9371 | "توليد_مفتاح_التوقيع_الكمي_من_البذرة"
9372 | "יצירת_מפתח_חתימה_קוונטי_מזרע"
9373 | "بیج_سے_کوانٹم_دستخط_کلید_تخلیق"
9374 | "génération_clé_signature_quantique_depuis_graine"
9375 | "quantensignaturschlüsselerzeugung_aus_saat"
9376 | "генерация_ключа_квантовой_подписи_из_семени" => {
9377 let seed = hex_to_32(&self.arg_str(&args, 0, ""));
9378 self.mldsa_ids.push(ling_crypto::MlDsa65Keypair::from_seed(seed));
9379 return Ok(Value::Number((self.mldsa_ids.len() - 1) as f64));
9380 },
9381 #[cfg(not(target_arch = "wasm32"))]
9382 "mldsa_public"
9383 | "양자서명공개키"
9384 | "กุญแจสาธารณะลายเซ็นควอนตัม"
9385 | "量子签名公钥"
9386 | "量子署名公開鍵"
9387 | "کلید_عمومی_امضای_کوانتومی"
9388 | "مفتاح_التوقيع_الكمي_العام"
9389 | "מפתח_חתימה_קוונטי_ציבורי"
9390 | "کوانٹم_دستخط_عوامی_کلید"
9391 | "clé_publique_signature_quantique"
9392 | "quantensignatur_öffentlicher_schlüssel"
9393 | "публичный_ключ_квантовой_подписи" => {
9394 let h = self.arg_num(&args, 0, 0.0)? as usize;
9395 let pk = self
9396 .mldsa_ids
9397 .get(h)
9398 .map(|kp| hex_encode(&kp.public_key()))
9399 .unwrap_or_default();
9400 return Ok(Value::Str(pk));
9401 },
9402 #[cfg(not(target_arch = "wasm32"))]
9404 "mldsa_sign"
9405 | "양자서명하다"
9406 | "เซ็นชื่อควอนตัม"
9407 | "量子签名"
9408 | "量子署名する"
9409 | "امضای_کوانتومی_کردن"
9410 | "توقيع_كمي"
9411 | "לחתום_קוונטית"
9412 | "کوانٹم_دستخط_کریں"
9413 | "signer_quantique"
9414 | "quantensignieren"
9415 | "подписать_квантово" => {
9416 let h = self.arg_num(&args, 0, 0.0)? as usize;
9417 let msg = self.arg_str(&args, 1, "");
9418 let sig = self
9419 .mldsa_ids
9420 .get(h)
9421 .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
9422 .unwrap_or_default();
9423 return Ok(Value::Str(sig));
9424 },
9425 #[cfg(not(target_arch = "wasm32"))]
9427 "mldsa_verify"
9428 | "양자서명확인"
9429 | "ยืนยันลายเซ็นควอนตัม"
9430 | "验证量子签名"
9431 | "量子署名検証"
9432 | "تایید_امضای_کوانتومی"
9433 | "التحقق_من_التوقيع_الكمي"
9434 | "אימות_חתימה_קוונטית"
9435 | "کوانٹم_دستخط_تصدیق"
9436 | "vérifier_signature_quantique"
9437 | "quantensignatur_verifizieren"
9438 | "проверить_квантовую_подпись" => {
9439 let pk_hex = self.arg_str(&args, 0, "");
9440 let msg = self.arg_str(&args, 1, "");
9441 let sig_hex = self.arg_str(&args, 2, "");
9442 let pk_bytes = hex_decode(&pk_hex);
9443 let sig_bytes = hex_decode(&sig_hex);
9444 let ok = ling_crypto::MlDsa65Keypair::verify(&pk_bytes, msg.as_bytes(), &sig_bytes)
9445 .is_ok();
9446 return Ok(Value::Bool(ok));
9447 },
9448 #[cfg(not(target_arch = "wasm32"))]
9451 "password_hash" | "비밀번호해시" => {
9452 let pw = self.arg_str(&args, 0, "");
9453 let hash = ling_crypto::Argon2idParams::default()
9454 .hash_password(pw.as_bytes())
9455 .unwrap_or_default();
9456 return Ok(Value::Str(hash));
9457 },
9458 #[cfg(not(target_arch = "wasm32"))]
9459 "password_verify" | "비밀번호확인" => {
9460 let pw = self.arg_str(&args, 0, "");
9461 let hash = self.arg_str(&args, 1, "");
9462 let ok = ling_crypto::Argon2idParams::verify_password(pw.as_bytes(), &hash).is_ok();
9463 return Ok(Value::Bool(ok));
9464 },
9465 #[cfg(not(target_arch = "wasm32"))]
9468 "random_hex" | "무작위16진수" => {
9469 use rand::RngCore;
9470 let n = self.arg_num(&args, 0, 16.0)?.max(0.0) as usize;
9471 let mut buf = vec![0u8; n];
9472 rand::rngs::OsRng.fill_bytes(&mut buf);
9473 return Ok(Value::Str(hex_encode(&buf)));
9474 },
9475 #[cfg(not(target_arch = "wasm32"))]
9479 "base64_encode" | "base64인코딩" => {
9480 use base64::Engine as _;
9481 let s = self.arg_str(&args, 0, "");
9482 return Ok(Value::Str(
9483 base64::engine::general_purpose::STANDARD.encode(s.as_bytes()),
9484 ));
9485 },
9486 #[cfg(not(target_arch = "wasm32"))]
9490 "base64_decode_to_file" | "base64파일로저장" => {
9491 use base64::Engine as _;
9492 let b64 = self.arg_str(&args, 0, "");
9493 let path = self.arg_str(&args, 1, "");
9494 let b64 = b64
9495 .split(',')
9496 .next_back()
9497 .unwrap_or(&b64); let ok = base64::engine::general_purpose::STANDARD
9499 .decode(b64)
9500 .ok()
9501 .and_then(|bytes| std::fs::write(&path, bytes).ok())
9502 .is_some();
9503 return Ok(Value::Bool(ok));
9504 },
9505 #[cfg(not(target_arch = "wasm32"))]
9510 "qr_svg" | "QR코드" => {
9511 let text = self.arg_str(&args, 0, "");
9512 let svg = qrcode::QrCode::new(text.as_bytes())
9513 .map(|code| {
9514 code.render::<qrcode::render::svg::Color>()
9515 .min_dimensions(240, 240)
9516 .dark_color(qrcode::render::svg::Color("#1a0f3d"))
9517 .light_color(qrcode::render::svg::Color("#ffffff"))
9518 .build()
9519 })
9520 .unwrap_or_default();
9521 return Ok(Value::Str(svg));
9522 },
9523 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9526 "zip_files" | "압축파일" => {
9527 let paths = match args.first() {
9528 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9529 _ => Vec::new(),
9530 };
9531 let out_path = self.arg_str(&args, 1, "out.zip");
9532 let ok = (|| -> std::io::Result<()> {
9533 let file = std::fs::File::create(&out_path)?;
9534 let mut writer = zip::ZipWriter::new(file);
9535 let options: zip::write::FileOptions<'_, ()> = zip::write::FileOptions::default()
9536 .compression_method(zip::CompressionMethod::Deflated);
9537 for p in &paths {
9538 let name = std::path::Path::new(p)
9539 .file_name()
9540 .map(|n| n.to_string_lossy().into_owned())
9541 .unwrap_or_else(|| p.clone());
9542 let bytes = std::fs::read(p)?;
9543 writer.start_file(name, options)?;
9544 std::io::Write::write_all(&mut writer, &bytes)?;
9545 }
9546 writer.finish()?;
9547 Ok(())
9548 })()
9549 .is_ok();
9550 return Ok(Value::Bool(ok));
9551 },
9552 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9559 "pdf_from_images" | "PDF来自图片" => {
9560 let paths = match args.first() {
9561 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9562 _ => Vec::new(),
9563 };
9564 let out_path = self.arg_str(&args, 1, "out.pdf");
9565 let ok = build_pdf_from_images(&paths, &out_path).is_ok();
9566 return Ok(Value::Bool(ok));
9567 },
9568
9569 "ease" => {
9573 let name = self.arg_str(&args, 0, "ease");
9574 let t = self.arg_num(&args, 1, 0.0)? as f32;
9575 return Ok(Value::Number(
9576 ling_ui::Easing::from_name(&name).apply(t) as f64
9577 ));
9578 },
9579
9580 "tween" | "补间" | "補間" | "트윈" | "แทรกค่า" | "میانفریم" | "تدرج_حركي" | "טווין" | "ٹوئین" | "твин" => {
9585 let a = self.arg_num(&args, 0, 0.0)?;
9586 let b = self.arg_num(&args, 1, 0.0)?;
9587 let t = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 1.0);
9588 return Ok(Value::Number(a + (b - a) * t));
9589 },
9590 "tween_ease" | "缓动补间" | "緩和補間" | "이징트윈" | "แทรกนุ่ม" | "میانفریم_نرم" | "تدرج_ناعم_حركي" | "טווין_חלק" | "ٹوئین_ایز" | "tween_lisse" | "tween_glättung" | "твин_плавность" =>
9591 {
9592 let a = self.arg_num(&args, 0, 0.0)? as f32;
9593 let b = self.arg_num(&args, 1, 0.0)? as f32;
9594 let t = self.arg_num(&args, 2, 0.0)? as f32;
9595 let kind = self.arg_str(&args, 3, "linear");
9596 let e = ling_animation::EaseFunction::from_name(&kind);
9597 return Ok(Value::Number(
9598 ling_animation::ease::tween_ease(&a, &b, t, e) as f64,
9599 ));
9600 },
9601 "breathe" | "呼吸" | "호흡" | "หายใจ" | "تنفس" | "נשימה" | "سانس" | "respirer" | "atmen" | "дышать" => {
9603 let t = self.arg_num(&args, 0, 0.0)? as f32;
9604 let rate = self.arg_num(&args, 1, 1.0)? as f32;
9605 let depth = self.arg_num(&args, 2, 0.1)? as f32;
9606 return Ok(Value::Number(
9607 ling_animation::scalar::breathe(t, rate, depth) as f64,
9608 ));
9609 },
9610 "wobble" | "摆动" | "揺れ" | "흔들림" | "โยก" | "نوسان" | "تذبذب" | "תנודה" | "لرزش" | "osciller" | "wackeln" | "покачивание" => {
9611 let t = self.arg_num(&args, 0, 0.0)? as f32;
9612 let freq = self.arg_num(&args, 1, 1.0)? as f32;
9613 let amp = self.arg_num(&args, 2, 1.0)? as f32;
9614 let phase = self.arg_num(&args, 3, 0.0)? as f32;
9615 return Ok(Value::Number(
9616 ling_animation::scalar::wobble(t, freq, amp, phase) as f64,
9617 ));
9618 },
9619 "gait_phase" | "步相" | "歩相" | "걸음위상" | "เฟสก้าว" | "فاز_گام" | "طور_المشية" | "שלב_הליכה" | "چال_مرحلہ" | "phase_démarche" | "gangphase" | "фаза_походки" => {
9620 let t = self.arg_num(&args, 0, 0.0)? as f32;
9621 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9622 return Ok(Value::Number(
9623 ling_animation::scalar::gait_phase(t, speed) as f64
9624 ));
9625 },
9626 "gait_swing" | "步摆" | "歩振り" | "걸음흔들" | "ก้าวแกว่ง" | "نوسان_گام" | "أرجحة_المشية" | "נדנוד_הליכה" | "چال_جھولا" | "balancement_démarche" | "gangschwung" | "мах_походки" =>
9627 {
9628 let t = self.arg_num(&args, 0, 0.0)? as f32;
9629 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9630 let stride = self.arg_num(&args, 2, 1.0)? as f32;
9631 return Ok(Value::Number(
9632 ling_animation::scalar::gait_swing(t, speed, stride) as f64,
9633 ));
9634 },
9635 "gait_lift" | "抬脚" | "足上げ" | "발들기" | "ยกเท้า" | "بلندشدن_گام" | "رفع_المشية" | "הרמת_הליכה" | "چال_اٹھاؤ" | "levée_démarche" | "ganghub" | "подъём_походки" => {
9636 let t = self.arg_num(&args, 0, 0.0)? as f32;
9637 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9638 let height = self.arg_num(&args, 2, 1.0)? as f32;
9639 return Ok(Value::Number(
9640 ling_animation::scalar::gait_lift(t, speed, height) as f64,
9641 ));
9642 },
9643 "spring_to" | "弹向" | "バネ寄せ" | "스프링이동" | "สปริงไป" | "فنر_بهسوی" | "نابض_إلى" | "קפיץ_אל" | "اسپرنگ_تک" | "ressort_vers" | "feder_zu" | "пружина_к" =>
9644 {
9645 let pos = self.arg_num(&args, 0, 0.0)? as f32;
9646 let vel = self.arg_num(&args, 1, 0.0)? as f32;
9647 let target = self.arg_num(&args, 2, 0.0)? as f32;
9648 let stiffness = self.arg_num(&args, 3, 120.0)? as f32;
9649 let damping = self.arg_num(&args, 4, 14.0)? as f32;
9650 let dt = self.arg_num(&args, 5, 1.0 / 60.0)? as f32;
9651 let (np, nv) =
9652 ling_animation::scalar::spring_step(pos, vel, target, stiffness, damping, dt);
9653 return Ok(Value::List(Rc::new(vec![
9654 Value::Number(np as f64),
9655 Value::Number(nv as f64),
9656 ])));
9657 },
9658 "ik2" | "反解" | "逆運動" | "역운동" | "ไอเค2" | "سینماتیک_معکوس2" | "حركية_عكسية2" | "קינמטיקה_הפוכה2" | "آئی_کے2" | "cinématique_inverse2" | "inverse_kinematik2" | "обратная_кинематика2" => {
9659 let l1 = self.arg_num(&args, 0, 1.0)? as f32;
9660 let l2 = self.arg_num(&args, 1, 1.0)? as f32;
9661 let tx = self.arg_num(&args, 2, 0.0)? as f32;
9662 let ty = self.arg_num(&args, 3, 0.0)? as f32;
9663 let (sh, el) = ling_animation::scalar::two_bone_ik(l1, l2, tx, ty);
9664 return Ok(Value::List(Rc::new(vec![
9665 Value::Number(sh as f64),
9666 Value::Number(el as f64),
9667 ])));
9668 },
9669 "gear_couple" | "齿轮联动" | "歯車連動" | "기어연동" | "เฟืองทด" | "جفت_چرخدنده" | "اقتران_التروس" | "צימוד_גלגלי_שיניים" | "گیئر_جوڑا" | "accoupler_engrenage" | "zahnrad_koppeln" | "сцепить_шестерни" =>
9671 {
9672 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9673 let ti = self.arg_num(&args, 1, 1.0)? as f32;
9674 let to = self.arg_num(&args, 2, 1.0)? as f32;
9675 return Ok(Value::Number(
9676 ling_animation::scalar::gear(angle, ti, to) as f64
9677 ));
9678 },
9679 "gear_train" | "齿轮组" | "歯車列" | "기어열" | "ชุดเฟือง" | "مجموعه_چرخدنده" | "قطار_التروس" | "שרשרת_גלגלי_שיניים" | "گیئر_ٹرین" | "train_engrenages" | "zahnradgetriebe" | "передача_шестерён" => {
9680 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9681 let teeth: Vec<f32> = match args.get(1) {
9682 Some(Value::List(items)) => items
9683 .iter()
9684 .filter_map(|v| {
9685 if let Value::Number(n) = v {
9686 Some(*n as f32)
9687 } else {
9688 None
9689 }
9690 })
9691 .collect(),
9692 _ => Vec::new(),
9693 };
9694 let out = ling_animation::mechanism::gear_train(angle, &teeth);
9695 return Ok(Value::List(Rc::new(
9696 out.into_iter().map(|a| Value::Number(a as f64)).collect(),
9697 )));
9698 },
9699 "cam_lift" | "凸轮升程" | "カム揚程" | "캠리프트" | "ยกลูกเบี้ยว" | "بلندشدن_بادامک" | "رفع_الكامة" | "הרמת_קאם" | "کیم_اٹھاؤ" | "levée_came" | "nockenhub" | "подъём_кулачка" =>
9700 {
9701 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9702 let lift = self.arg_num(&args, 1, 1.0)? as f32;
9703 return Ok(Value::Number(
9704 ling_animation::scalar::cam_lift(angle, lift) as f64
9705 ));
9706 },
9707 "piston" | "活塞" | "ピストン" | "피스톤" | "ลูกสูบ" | "پیستون" | "مكبس" | "בוכנה" | "پسٹن" | "kolben" | "поршень" => {
9708 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9709 let crank = self.arg_num(&args, 1, 1.0)? as f32;
9710 let rod = self.arg_num(&args, 2, 2.0)? as f32;
9711 return Ok(Value::Number(
9712 ling_animation::scalar::piston(angle, crank, rod) as f64,
9713 ));
9714 },
9715 "rack" | "齿条" | "ラック" | "랙" | "แร็ค" | "زبانهدنده" | "سكة_مسننة" | "מוט_שיניים" | "ریک" | "crémaillère" | "zahnstange" | "рейка" => {
9716 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9717 let radius = self.arg_num(&args, 1, 1.0)? as f32;
9718 return Ok(Value::Number(
9719 ling_animation::scalar::rack(angle, radius) as f64
9720 ));
9721 },
9722 #[cfg(not(target_arch = "wasm32"))]
9723 "mouse_x" => {
9724 let gfx = self.gfx.borrow();
9725 let v = gfx
9726 .window
9727 .as_ref()
9728 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9729 .map(|p| p.0 as f64)
9730 .unwrap_or(0.0);
9731 return Ok(Value::Number(v));
9732 },
9733 #[cfg(target_arch = "wasm32")]
9734 "mouse_x" => {
9735 return Ok(Value::Number(crate::gfx::wasm_mouse_x() as f64));
9736 },
9737 #[cfg(not(target_arch = "wasm32"))]
9738 "mouse_y" => {
9739 let gfx = self.gfx.borrow();
9740 let v = gfx
9741 .window
9742 .as_ref()
9743 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9744 .map(|p| p.1 as f64)
9745 .unwrap_or(0.0);
9746 return Ok(Value::Number(v));
9747 },
9748 #[cfg(target_arch = "wasm32")]
9749 "mouse_y" => {
9750 return Ok(Value::Number(crate::gfx::wasm_mouse_y() as f64));
9751 },
9752 #[cfg(not(target_arch = "wasm32"))]
9753 "mouse_down" => {
9754 let mut gfx = self.gfx.borrow_mut();
9755 let d = !gfx.input_suppressed()
9756 && gfx
9757 .window
9758 .as_ref()
9759 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
9760 .unwrap_or(false);
9761 return Ok(Value::Bool(d));
9762 },
9763 #[cfg(target_arch = "wasm32")]
9764 "mouse_down" => {
9765 return Ok(Value::Bool(crate::gfx::wasm_mouse_down()));
9766 },
9767 #[cfg(not(target_arch = "wasm32"))]
9768 "mouse_down_right" | "เมาส์ขวา" | "ماوس_راست_فشرده" | "الفأرة_اليمنى_مضغوطة" | "עכבר_ימני_לחוץ" | "دایاں_ماؤس_دبا_ہوا" => {
9769 let mut gfx = self.gfx.borrow_mut();
9770 let d = !gfx.input_suppressed()
9771 && gfx
9772 .window
9773 .as_ref()
9774 .map(|w| w.get_mouse_down(minifb::MouseButton::Right))
9775 .unwrap_or(false);
9776 return Ok(Value::Bool(d));
9777 },
9778 #[cfg(target_arch = "wasm32")]
9779 "mouse_down_right" | "เมาส์ขวา" | "ماوس_راست_فشرده" | "الفأرة_اليمنى_مضغوطة" | "עכבר_ימני_לחוץ" | "دایاں_ماؤس_دبا_ہوا" => {
9780 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_right()));
9781 },
9782 #[cfg(not(target_arch = "wasm32"))]
9783 "mouse_down_middle" | "เมาส์กลาง" | "ماوس_وسط_فشرده" | "الفأرة_الوسطى_مضغوطة" | "עכבר_אמצעי_לחוץ" | "درمیانی_ماؤس_دبا_ہوا" => {
9784 let mut gfx = self.gfx.borrow_mut();
9785 let d = !gfx.input_suppressed()
9786 && gfx
9787 .window
9788 .as_ref()
9789 .map(|w| w.get_mouse_down(minifb::MouseButton::Middle))
9790 .unwrap_or(false);
9791 return Ok(Value::Bool(d));
9792 },
9793 #[cfg(target_arch = "wasm32")]
9794 "mouse_down_middle" | "เมาส์กลาง" | "ماوس_وسط_فشرده" | "الفأرة_الوسطى_مضغوطة" | "עכבר_אמצעי_לחוץ" | "درمیانی_ماؤس_دبا_ہوا" => {
9795 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_middle()));
9796 },
9797 #[cfg(not(target_arch = "wasm32"))]
9798 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" | "ناحیه_فعال" | "منطقة_ساخنة" | "אזור_חם" | "ہاٹ_زون" | "survol_ui" | "ui_hover" | "ui_наведение" =>
9799 {
9800 let x = self.arg_num(&args, 0, 0.0)? as f32;
9801 let y = self.arg_num(&args, 1, 0.0)? as f32;
9802 let w = self.arg_num(&args, 2, 0.0)? as f32;
9803 let h = self.arg_num(&args, 3, 0.0)? as f32;
9804 let gfx = self.gfx.borrow();
9805 let (mx, my) = gfx
9806 .window
9807 .as_ref()
9808 .and_then(|win| win.get_mouse_pos(minifb::MouseMode::Clamp))
9809 .unwrap_or((0.0, 0.0));
9810 return Ok(Value::Bool(ling_ui::holo::hit_rect(mx, my, x, y, w, h)));
9811 },
9812 #[cfg(target_arch = "wasm32")]
9813 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" | "ناحیه_فعال" | "منطقة_ساخنة" | "אזור_חם" | "ہاٹ_زون" | "survol_ui" | "ui_hover" | "ui_наведение" =>
9814 {
9815 return Ok(Value::Bool(false));
9816 },
9817 "ui_text" | "界面文字" | "UI文字" | "UI텍스트" | "ข้อความหน้าจอ" | "متن_رابط" | "نص_الواجهة" | "טקסט_ממשק" | "یو_آئی_متن" | "texte_ui" | "ui_beschriftung" | "ui_текст" =>
9819 {
9820 let x = self.arg_num(&args, 0, 0.0)? as f32;
9821 let y = self.arg_num(&args, 1, 0.0)? as f32;
9822 let scale = self.arg_num(&args, 2, 16.0)? as f32;
9823 let s = self.arg_str(&args, 3, "");
9824 let segs = ling_ui::holo::text_lines(&s, x, y, scale * 0.62, scale, scale * 0.24);
9825 let mut gfx = self.gfx.borrow_mut();
9826 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
9827 for sg in segs {
9828 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
9829 }
9830 return Ok(Value::Unit);
9831 },
9832 #[cfg(not(target_arch = "wasm32"))]
9835 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" | "بارگذاری_فونت" | "تحميل_الخط" | "טעינת_גופן" | "فونٹ_لوڈ" | "charger_police" | "schriftart_laden" | "загрузить_шрифт" =>
9836 {
9837 let path = self.arg_str(&args, 0, "");
9838 let weight = match self.arg_num(&args, 1, 0.0)? {
9840 w if w > 0.0 => Some(w as f32),
9841 _ => None,
9842 };
9843 let mut loaded = ling_graphics::VectorFont::from_path_weight(&path, weight);
9845 if loaded.is_err() {
9846 if let Some(dir) = &self.source_dir {
9847 let joined = dir.join(&path);
9848 loaded = ling_graphics::VectorFont::from_path_weight(
9849 &joined.to_string_lossy(),
9850 weight,
9851 );
9852 }
9853 }
9854 match loaded {
9855 Ok(f) => {
9856 let id = self.fonts.len();
9857 self.fonts.push(f);
9858 return Ok(Value::Number(id as f64));
9859 },
9860 Err(e) => {
9861 eprintln!("font_load failed ({path}): {e}");
9862 return Ok(Value::Number(-1.0));
9863 },
9864 }
9865 },
9866 #[cfg(target_arch = "wasm32")]
9867 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" | "بارگذاری_فونت" | "تحميل_الخط" | "טעינת_גופן" | "فونٹ_لوڈ" | "charger_police" | "schriftart_laden" | "загрузить_шрифт" =>
9868 {
9869 return Ok(Value::Number(-1.0));
9872 },
9873 #[cfg(not(target_arch = "wasm32"))]
9878 "image_load" =>
9879 {
9880 let path = self.arg_str(&args, 0, "").replace('\\', "/");
9881 let mut loaded = image::open(&path);
9882 if loaded.is_err() {
9883 if let Some(dir) = &self.source_dir {
9884 let joined = dir.join(&path);
9885 loaded = image::open(&joined);
9886 }
9887 }
9888 match loaded {
9889 Ok(img) => {
9890 let id = self.images.len();
9891 self.images.push(img.to_rgba8());
9892 return Ok(Value::Number(id as f64));
9893 },
9894 Err(e) => {
9895 eprintln!("image_load failed ({path}): {e}");
9896 return Ok(Value::Number(-1.0));
9897 },
9898 }
9899 },
9900 #[cfg(target_arch = "wasm32")]
9901 "image_load" =>
9902 {
9903 return Ok(Value::Number(-1.0));
9905 },
9906 "image_width" =>
9907 {
9908 let id = self.arg_num(&args, 0, -1.0)? as i64;
9909 if id >= 0 && (id as usize) < self.images.len() {
9910 return Ok(Value::Number(self.images[id as usize].width() as f64));
9911 }
9912 return Ok(Value::Number(0.0));
9913 },
9914 "image_height" =>
9915 {
9916 let id = self.arg_num(&args, 0, -1.0)? as i64;
9917 if id >= 0 && (id as usize) < self.images.len() {
9918 return Ok(Value::Number(self.images[id as usize].height() as f64));
9919 }
9920 return Ok(Value::Number(0.0));
9921 },
9922 "image_pixel_r" | "image_pixel_g" | "image_pixel_b" | "image_pixel_a" =>
9923 {
9924 let id = self.arg_num(&args, 0, -1.0)? as i64;
9925 let px = self.arg_num(&args, 1, 0.0)? as i64;
9926 let py = self.arg_num(&args, 2, 0.0)? as i64;
9927 if id >= 0 && (id as usize) < self.images.len() {
9928 let img = &self.images[id as usize];
9929 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
9930 let p = img.get_pixel(px as u32, py as u32);
9931 let ch = match name {
9932 "image_pixel_r" => p[0],
9933 "image_pixel_g" => p[1],
9934 "image_pixel_b" => p[2],
9935 _ => p[3],
9936 };
9937 return Ok(Value::Number(ch as f64));
9938 }
9939 }
9940 return Ok(Value::Number(0.0));
9941 },
9942 "image_new" =>
9951 {
9952 let w = self.arg_num(&args, 0, 1.0)?.max(1.0) as u32;
9953 let h = self.arg_num(&args, 1, 1.0)?.max(1.0) as u32;
9954 let id = self.images.len();
9955 self.images.push(image::RgbaImage::new(w, h));
9956 return Ok(Value::Number(id as f64));
9957 },
9958 "image_set_pixel" =>
9963 {
9964 let id = self.arg_num(&args, 0, -1.0)? as i64;
9965 let px = self.arg_num(&args, 1, 0.0)? as i64;
9966 let py = self.arg_num(&args, 2, 0.0)? as i64;
9967 let r = self.arg_num(&args, 3, 0.0)?.clamp(0.0, 255.0) as u8;
9968 let g = self.arg_num(&args, 4, 0.0)?.clamp(0.0, 255.0) as u8;
9969 let b = self.arg_num(&args, 5, 0.0)?.clamp(0.0, 255.0) as u8;
9970 let a = self.arg_num(&args, 6, 255.0)?.clamp(0.0, 255.0) as u8;
9971 if id >= 0 && (id as usize) < self.images.len() {
9972 let img = &mut self.images[id as usize];
9973 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
9974 img.put_pixel(px as u32, py as u32, image::Rgba([r, g, b, a]));
9975 }
9976 }
9977 return Ok(Value::Unit);
9978 },
9979 #[cfg(not(target_arch = "wasm32"))]
9987 "image_save" =>
9988 {
9989 let id = self.arg_num(&args, 0, -1.0)? as i64;
9990 let path = self.arg_str(&args, 1, "");
9991 if id >= 0 && (id as usize) < self.images.len() {
9992 if let Some(parent) = std::path::Path::new(&path).parent() {
9993 if !parent.as_os_str().is_empty() {
9994 let _ = std::fs::create_dir_all(parent);
9995 }
9996 }
9997 if self.images[id as usize].save(&path).is_ok() {
9998 return Ok(Value::Number(1.0));
9999 }
10000 }
10001 return Ok(Value::Number(-1.0));
10002 },
10003 #[cfg(target_arch = "wasm32")]
10004 "image_save" =>
10005 {
10006 return Ok(Value::Number(-1.0));
10007 },
10008 #[cfg(not(target_arch = "wasm32"))]
10018 "image_draw" =>
10019 {
10020 let id = self.arg_num(&args, 0, -1.0)? as i64;
10021 let dx = self.arg_num(&args, 1, 0.0)? as i32;
10022 let dy = self.arg_num(&args, 2, 0.0)? as i32;
10023 let dw = self.arg_num(&args, 3, 0.0)?.max(0.0) as i32;
10024 let dh = self.arg_num(&args, 4, 0.0)?.max(0.0) as i32;
10025 if id >= 0 && (id as usize) < self.images.len() && dw > 0 && dh > 0 {
10026 let img = &self.images[id as usize];
10027 let sw = img.width() as i32;
10028 let sh = img.height() as i32;
10029 if sw > 0 && sh > 0 {
10030 let mut gfx = self.gfx.borrow_mut();
10031 let (fw, fh) = (gfx.width as i32, gfx.height as i32);
10032 for py in 0..dh {
10033 let ty = dy + py;
10034 if ty < 0 || ty >= fh {
10035 continue;
10036 }
10037 let sy = (py * sh / dh).clamp(0, sh - 1) as u32;
10038 for px in 0..dw {
10039 let tx = dx + px;
10040 if tx < 0 || tx >= fw {
10041 continue;
10042 }
10043 let sx = (px * sw / dw).clamp(0, sw - 1) as u32;
10044 let p = img.get_pixel(sx, sy);
10045 let a = p[3] as u32;
10046 if a == 0 {
10047 continue;
10048 }
10049 let idx = ty as usize * gfx.width + tx as usize;
10050 if a >= 255 {
10051 gfx.buffer[idx] =
10052 ((p[0] as u32) << 16) | ((p[1] as u32) << 8) | (p[2] as u32);
10053 } else {
10054 let bg = gfx.buffer[idx];
10055 let br = (bg >> 16) & 0xff;
10056 let bg_g = (bg >> 8) & 0xff;
10057 let bb = bg & 0xff;
10058 let r = (p[0] as u32 * a + br * (255 - a)) / 255;
10059 let g = (p[1] as u32 * a + bg_g * (255 - a)) / 255;
10060 let b = (p[2] as u32 * a + bb * (255 - a)) / 255;
10061 gfx.buffer[idx] = (r << 16) | (g << 8) | b;
10062 }
10063 }
10064 }
10065 }
10066 }
10067 return Ok(Value::Unit);
10068 },
10069 #[cfg(target_arch = "wasm32")]
10070 "image_draw" =>
10071 {
10072 return Ok(Value::Unit);
10073 },
10074 #[cfg(not(target_arch = "wasm32"))]
10077 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" | "متن_فونت" | "نص_الخط" | "טקסט_גופן" | "فونٹ_متن" | "texte_police" | "schriftart_text" | "текст_шрифт" =>
10078 {
10079 let id = self.arg_num(&args, 0, 0.0)? as i64;
10080 let x = self.arg_num(&args, 1, 0.0)? as f32;
10081 let y = self.arg_num(&args, 2, 0.0)? as f32;
10082 let px = self.arg_num(&args, 3, 16.0)? as f32;
10083 let s = self.arg_str(&args, 4, "");
10084 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
10085 let strokes = self.font_layout_2d(id as usize, x, y, px, &s);
10086 let mut gfx = self.gfx.borrow_mut();
10087 let (w, h, color, add, aa) =
10088 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
10089 for pl in &strokes {
10090 for seg in pl.windows(2) {
10091 if aa {
10092 crate::gfx::raster::draw_line_aa(
10093 &mut gfx.buffer,
10094 w,
10095 h,
10096 color,
10097 add,
10098 seg[0][0],
10099 seg[0][1],
10100 seg[1][0],
10101 seg[1][1],
10102 );
10103 } else {
10104 crate::gfx::raster::draw_line(
10105 &mut gfx.buffer,
10106 w,
10107 h,
10108 color,
10109 seg[0][0],
10110 seg[0][1],
10111 seg[1][0],
10112 seg[1][1],
10113 );
10114 }
10115 }
10116 }
10117 }
10118 return Ok(Value::Unit);
10119 },
10120 #[cfg(target_arch = "wasm32")]
10121 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" | "متن_فونت" | "نص_الخط" | "טקסט_גופן" | "فونٹ_متن" | "texte_police" | "schriftart_text" | "текст_шрифт" =>
10122 {
10123 return Ok(Value::Unit);
10124 },
10125 #[cfg(not(target_arch = "wasm32"))]
10128 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" | "پرکردن_متن_فونت" | "تعبئة_نص_الخط" | "מילוי_טקסט_גופן" | "فونٹ_متن_بھرو" | "remplir_texte_police" | "schriftart_text_füllen" | "заполнить_текст_шрифт" =>
10129 {
10130 let id = self.arg_num(&args, 0, 0.0)? as i64;
10131 let x = self.arg_num(&args, 1, 0.0)? as f32;
10132 let y = self.arg_num(&args, 2, 0.0)? as f32;
10133 let px = self.arg_num(&args, 3, 16.0)? as f32;
10134 let s = self.arg_str(&args, 4, "");
10135 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
10136 let glyphs = self.font_layout_2d_glyphs(id as usize, x, y, px, &s);
10138 let mut gfx = self.gfx.borrow_mut();
10139 let (w, h, color, add, aa) =
10140 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
10141 for contours in &glyphs {
10142 if aa {
10143 crate::gfx::raster::fill_contours_aa(
10144 &mut gfx.buffer,
10145 w,
10146 h,
10147 color,
10148 add,
10149 contours,
10150 );
10151 } else {
10152 crate::gfx::raster::fill_contours(
10153 &mut gfx.buffer,
10154 w,
10155 h,
10156 color,
10157 add,
10158 contours,
10159 );
10160 }
10161 }
10162 }
10163 return Ok(Value::Unit);
10164 },
10165 #[cfg(target_arch = "wasm32")]
10166 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" | "پرکردن_متن_فونت" | "تعبئة_نص_الخط" | "מילוי_טקסט_גופן" | "فونٹ_متن_بھرو" | "remplir_texte_police" | "schriftart_text_füllen" | "заполнить_текст_шрифт" =>
10167 {
10168 return Ok(Value::Unit);
10169 },
10170 #[cfg(not(target_arch = "wasm32"))]
10174 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" | "متن_فونت_سهبعدی" | "نص_خط_ثلاثي_الأبعاد" | "טקסט_גופן_תלת_ממדי" | "تھری_ڈی_فونٹ_متن" | "texte_police_3d" | "schriftart_text_3d" | "текст_шрифт_3d" =>
10175 {
10176 let id = self.arg_num(&args, 0, 0.0)? as i64;
10177 let cx = self.arg_num(&args, 1, 0.0)? as f32;
10178 let cy = self.arg_num(&args, 2, 0.0)? as f32;
10179 let cz = self.arg_num(&args, 3, 0.0)? as f32;
10180 let ux = self.arg_num(&args, 4, 1.0)? as f32;
10181 let uy = self.arg_num(&args, 5, 0.0)? as f32;
10182 let uz = self.arg_num(&args, 6, 0.0)? as f32;
10183 let vx = self.arg_num(&args, 7, 0.0)? as f32;
10184 let vy = self.arg_num(&args, 8, 1.0)? as f32;
10185 let vz = self.arg_num(&args, 9, 0.0)? as f32;
10186 let size = self.arg_num(&args, 10, 1.0)? as f32;
10187 let s = self.arg_str(&args, 11, "");
10188 let fill_rows = self.arg_num(&args, 12, 0.0)? as i32;
10192 if id >= 0 && (id as usize) < self.fonts.len() && size > 0.0 {
10193 let font = &mut self.fonts[id as usize];
10195 let asc = font.ascent();
10196 let mut pen = 0.0f32;
10197 let mut lines: Vec<[f32; 6]> = Vec::new();
10198 for ch in s.chars() {
10199 let go = font.glyph_outline(ch, 0.01);
10200 let map = |p: [f32; 2], pen: f32| {
10201 let a = pen + p[0];
10202 let b = p[1] - asc; [
10204 cx + a * size * ux + b * size * vx,
10205 cy + a * size * uy + b * size * vy,
10206 cz + a * size * uz + b * size * vz,
10207 ]
10208 };
10209 for pl in &go.polylines {
10210 for seg in pl.windows(2) {
10211 let p0 = map(seg[0], pen);
10212 let p1 = map(seg[1], pen);
10213 lines.push([p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]]);
10214 }
10215 }
10216 if fill_rows > 0 {
10217 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
10221 for pl in &go.polylines {
10222 for p in pl {
10223 ymin = ymin.min(p[1]);
10224 ymax = ymax.max(p[1]);
10225 }
10226 }
10227 if ymax > ymin {
10228 for r in 0..fill_rows {
10229 let y =
10230 ymin + (r as f32 + 0.5) * (ymax - ymin) / fill_rows as f32;
10231 let mut xs: Vec<f32> = Vec::new();
10232 for pl in &go.polylines {
10233 let n = pl.len();
10234 if n < 2 {
10235 continue;
10236 }
10237 for k in 0..n {
10238 let p0 = pl[k];
10239 let p1 = pl[(k + 1) % n];
10240 if k + 1 == n
10241 && (p1[0] - p0[0]).abs() < 1e-6
10242 && (p1[1] - p0[1]).abs() < 1e-6
10243 {
10244 continue; }
10246 let (y0, y1) = (p0[1], p1[1]);
10247 if (y0 <= y && y1 > y) || (y1 <= y && y0 > y) {
10248 let t = (y - y0) / (y1 - y0);
10249 xs.push(p0[0] + t * (p1[0] - p0[0]));
10250 }
10251 }
10252 }
10253 xs.sort_by(|a, b| {
10254 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
10255 });
10256 let mut k = 0;
10257 while k + 1 < xs.len() {
10258 let a = map([xs[k], y], pen);
10259 let b = map([xs[k + 1], y], pen);
10260 lines.push([a[0], a[1], a[2], b[0], b[1], b[2]]);
10261 k += 2;
10262 }
10263 }
10264 }
10265 }
10266 pen += go.advance;
10267 }
10268 let mut gfx = self.gfx.borrow_mut();
10269 let color = gfx.color;
10270 let near = -gfx.camera.zdist + 0.05;
10271 for l in &lines {
10272 let (mut ax, mut ay, mut az) = (l[0], l[1], l[2]);
10273 let (mut bx, mut by, mut bz) = (l[3], l[4], l[5]);
10274 let da = gfx.camera.depth(ax, ay, az);
10275 let db = gfx.camera.depth(bx, by, bz);
10276 if da <= near && db <= near {
10277 continue;
10278 }
10279 if da <= near {
10280 let t = (near - da) / (db - da);
10281 ax += t * (bx - ax);
10282 ay += t * (by - ay);
10283 az += t * (bz - az);
10284 } else if db <= near {
10285 let t = (near - da) / (db - da);
10286 bx = ax + t * (bx - ax);
10287 by = ay + t * (by - ay);
10288 bz = az + t * (bz - az);
10289 }
10290 let (sax, say, da2) = gfx.camera.project(ax, ay, az);
10291 let (sbx, sby, db2) = gfx.camera.project(bx, by, bz);
10292 let depth = (da2 + db2) / 2.0;
10293 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
10294 }
10295 }
10296 return Ok(Value::Unit);
10297 },
10298 #[cfg(target_arch = "wasm32")]
10299 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" | "متن_فونت_سهبعدی" | "نص_خط_ثلاثي_الأبعاد" | "טקסט_גופן_תלת_ממדי" | "تھری_ڈی_فونٹ_متن" | "texte_police_3d" | "schriftart_text_3d" | "текст_шрифт_3d" =>
10300 {
10301 return Ok(Value::Unit);
10302 },
10303 #[cfg(not(target_arch = "wasm32"))]
10305 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" | "عرض_فونت" | "عرض_الخط" | "רוחב_גופן" | "فونٹ_چوڑائی" | "largeur_police" | "schriftart_breite" | "ширина_шрифта" =>
10306 {
10307 let id = self.arg_num(&args, 0, 0.0)? as i64;
10308 let px = self.arg_num(&args, 1, 16.0)? as f32;
10309 let s = self.arg_str(&args, 2, "");
10310 if id >= 0 && (id as usize) < self.fonts.len() {
10311 return Ok(Value::Number(self.fonts[id as usize].measure(&s, px) as f64));
10312 }
10313 return Ok(Value::Number(0.0));
10314 },
10315 #[cfg(target_arch = "wasm32")]
10316 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" | "عرض_فونت" | "عرض_الخط" | "רוחב_גופן" | "فونٹ_چوڑائی" | "largeur_police" | "schriftart_breite" | "ширина_шрифта" =>
10317 {
10318 return Ok(Value::Number(0.0));
10319 },
10320 #[cfg(not(target_arch = "wasm32"))]
10325 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10326 | "フォント輪郭" | "글꼴윤곽" | "خطدور_نویسه_فونت" | "حدود_حرف_الخط" | "קו_מתאר_גליף" | "فونٹ_گلف_آؤٹ_لائن" => {
10327 let id = self.arg_num(&args, 0, 0.0)? as i64;
10328 let s = self.arg_str(&args, 1, "");
10329 let tol = self.arg_num(&args, 2, 0.01)? as f32;
10330 let ch = s.chars().next().unwrap_or(' ');
10331 if id >= 0 && (id as usize) < self.fonts.len() {
10332 let go = self.fonts[id as usize].glyph_outline(ch, tol.max(1e-4));
10333 let mut contours: Vec<Value> = Vec::with_capacity(go.polylines.len());
10334 for pl in &go.polylines {
10335 let mut flat: Vec<Value> = Vec::with_capacity(pl.len() * 2);
10336 for p in pl {
10337 flat.push(Value::Number(p[0] as f64));
10338 flat.push(Value::Number(p[1] as f64));
10339 }
10340 contours.push(Value::List(Rc::new(flat)));
10341 }
10342 return Ok(Value::List(Rc::new(contours)));
10343 }
10344 return Ok(Value::List(Rc::new(vec![])));
10345 },
10346 #[cfg(target_arch = "wasm32")]
10347 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10348 | "フォント輪郭" | "글꼴윤곽" | "خطدور_نویسه_فونت" | "حدود_حرف_الخط" | "קו_מתאר_גליף" | "فونٹ_گلف_آؤٹ_لائن" => {
10349 return Ok(Value::List(Rc::new(vec![])));
10350 },
10351 #[cfg(not(target_arch = "wasm32"))]
10354 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" | "پیشروی_فونت" | "تقدم_الخط" | "קידום_גופן" | "فونٹ_ایڈوانس" => {
10355 let id = self.arg_num(&args, 0, 0.0)? as i64;
10356 let s = self.arg_str(&args, 1, "");
10357 let ch = s.chars().next().unwrap_or(' ');
10358 if id >= 0 && (id as usize) < self.fonts.len() {
10359 return Ok(Value::Number(self.fonts[id as usize].advance(ch) as f64));
10360 }
10361 return Ok(Value::Number(0.0));
10362 },
10363 #[cfg(target_arch = "wasm32")]
10364 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" | "پیشروی_فونت" | "تقدم_الخط" | "קידום_גופן" | "فونٹ_ایڈوانس" => {
10365 return Ok(Value::Number(0.0));
10366 },
10367
10368 "ui_frame" | "边框" | "フレーム枠" | "프레임틀" | "กรอบ" | "قاب_رابط" | "إطار_الواجهة" | "מסגרת_ממשק" | "یو_آئی_فریم" | "cadre_ui" | "ui_rahmen" | "ui_рамка" => {
10370 let x = self.arg_num(&args, 0, 0.0)? as f32;
10371 let y = self.arg_num(&args, 1, 0.0)? as f32;
10372 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10373 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10374 let l = self.arg_num(&args, 4, 14.0)? as f32;
10375 let segs = ling_ui::holo::corner_brackets(x, y, w0, h0, l);
10376 let mut gfx = self.gfx.borrow_mut();
10377 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10378 for sg in segs {
10379 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10380 }
10381 return Ok(Value::Unit);
10382 },
10383 "ui_bevel" | "斜角框" | "ベベル枠" | "베벨틀" | "กรอบเฉียง" | "لبه_شیبدار" | "حافة_مشطوفة" | "מסגרת_משופעת" | "یو_آئی_بیول" | "biseau_ui" | "ui_fase" | "ui_фаска" =>
10385 {
10386 let x = self.arg_num(&args, 0, 0.0)? as f32;
10387 let y = self.arg_num(&args, 1, 0.0)? as f32;
10388 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10389 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10390 let bv = self.arg_num(&args, 4, 10.0)? as f32;
10391 let segs = ling_ui::holo::beveled_rect(x, y, w0, h0, bv);
10392 let mut gfx = self.gfx.borrow_mut();
10393 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10394 for sg in segs {
10395 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10396 }
10397 return Ok(Value::Unit);
10398 },
10399
10400 #[cfg(not(target_arch = "wasm32"))]
10406 "ui_theme" | "界面主题" | "UIテーマ" | "인터페이스테마" | "ธีมส่วนติดต่อ" | "پوسته_رابط" | "سمة_الواجهة" | "ערכת_נושא" | "یو_آئی_تھیم" | "thème_ui" | "ui_thema" | "ui_тема" =>
10407 {
10408 let cur = self.ui_theme;
10409 let primary = self.color_at(&args, 0, cur.primary);
10410 let accent = self.color_at(&args, 3, cur.accent);
10411 let track = self.color_at(&args, 6, cur.track);
10412 let warn = self.color_at(&args, 9, cur.warn);
10413 let text = self.color_at(&args, 12, cur.text);
10414 let bg = self.color_at(&args, 15, cur.bg);
10415 self.ui_theme = UiTheme { primary, accent, track, warn, text, bg };
10416 return Ok(Value::Unit);
10417 },
10418
10419 "ui_theme_colors" | "인터페이스테마색상" => {
10425 let th = self.ui_theme;
10426 let mut out = Vec::with_capacity(18);
10427 for c in [th.primary, th.accent, th.track, th.warn, th.text, th.bg] {
10428 out.push(Value::Number(((c >> 16) & 0xFF) as f64));
10429 out.push(Value::Number(((c >> 8) & 0xFF) as f64));
10430 out.push(Value::Number((c & 0xFF) as f64));
10431 }
10432 return Ok(Value::List(Rc::new(out)));
10433 },
10434
10435 #[cfg(not(target_arch = "wasm32"))]
10437 "ui_radar" | "雷达" | "レーダー" | "레이더" | "เรดาร์" | "رادار_رابط" | "رادار_الواجهة" | "מכ״ם_ממשק" | "یو_آئی_ریڈار" | "radar_ui" | "ui_радар" => {
10438 let cx = self.arg_num(&args, 0, 0.)? as f32;
10439 let cy = self.arg_num(&args, 1, 0.)? as f32;
10440 let r = self.arg_num(&args, 2, 60.)? as f32;
10441 let sweep = self.arg_num(&args, 3, 0.)? as f32;
10442 let th = self.ui_theme;
10443 let prim = self.color_at(&args, 4, th.primary);
10444 self.draw_ui(&ling_ui::widgets::radar(
10445 cx, cy, r, sweep, prim, th.accent, th.track,
10446 ));
10447 return Ok(Value::Unit);
10448 },
10449 #[cfg(not(target_arch = "wasm32"))]
10450 "ui_compass" | "罗盘" | "コンパス" | "나침반" | "เข็มทิศ" | "قطبنمای_رابط" | "بوصلة_الواجهة" | "מצפן_ממשק" | "یو_آئی_قطب_نما" | "boussole_ui" | "ui_kompass" | "ui_компас" => {
10451 let x = self.arg_num(&args, 0, 0.)? as f32;
10452 let y = self.arg_num(&args, 1, 0.)? as f32;
10453 let w0 = self.arg_num(&args, 2, 300.)? as f32;
10454 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10455 let head = self.arg_num(&args, 4, 0.)? as f32;
10456 let th = self.ui_theme;
10457 let prim = self.color_at(&args, 5, th.primary);
10458 self.draw_ui(&ling_ui::widgets::compass(
10459 x, y, w0, h0, head, prim, th.track,
10460 ));
10461 return Ok(Value::Unit);
10462 },
10463 #[cfg(not(target_arch = "wasm32"))]
10464 "ui_reticle" | "准星" | "照準" | "조준선" | "เป้าเล็ง" | "نشانه_رابط" | "علامة_تصويب" | "כוונת" | "نشانہ" | "réticule_ui" | "ui_fadenkreuz" | "ui_прицел" => {
10465 let cx = self.arg_num(&args, 0, 0.)? as f32;
10466 let cy = self.arg_num(&args, 1, 0.)? as f32;
10467 let r = self.arg_num(&args, 2, 30.)? as f32;
10468 let spread = self.arg_num(&args, 3, 0.)? as f32;
10469 let th = self.ui_theme;
10470 let prim = self.color_at(&args, 4, th.primary);
10471 self.draw_ui(&ling_ui::widgets::reticle(cx, cy, r, spread, prim));
10472 return Ok(Value::Unit);
10473 },
10474 #[cfg(not(target_arch = "wasm32"))]
10475 "ui_target" | "锁定框" | "ターゲット" | "표적" | "กรอบเป้า" | "قاب_هدف" | "إطار_الهدف" | "מסגרת_מטרה" | "ہدف_فریم" | "cible_ui" | "ui_ziel" | "ui_цель" =>
10476 {
10477 let x = self.arg_num(&args, 0, 0.)? as f32;
10478 let y = self.arg_num(&args, 1, 0.)? as f32;
10479 let w0 = self.arg_num(&args, 2, 80.)? as f32;
10480 let h0 = self.arg_num(&args, 3, 80.)? as f32;
10481 let lock = self.arg_num(&args, 4, 0.)? as f32;
10482 let th = self.ui_theme;
10483 let prim = self.color_at(&args, 5, th.primary);
10484 self.draw_ui(&ling_ui::widgets::target(
10485 x, y, w0, h0, lock, prim, th.accent,
10486 ));
10487 return Ok(Value::Unit);
10488 },
10489 #[cfg(not(target_arch = "wasm32"))]
10490 "ui_panel" | "面板" | "パネル" | "패널" | "แผง" | "پنل_رابط" | "لوحة_الواجهة" | "לוח_ממשק" | "یو_آئی_پینل" | "panneau_ui" | "ui_feld" | "ui_панель" => {
10491 let x = self.arg_num(&args, 0, 0.)? as f32;
10492 let y = self.arg_num(&args, 1, 0.)? as f32;
10493 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10494 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10495 let bv = self.arg_num(&args, 4, 12.)? as f32;
10496 let th = self.ui_theme;
10497 let prim = self.color_at(&args, 5, th.primary);
10498 self.draw_ui(&ling_ui::widgets::panel(x, y, w0, h0, bv, prim, th.bg));
10499 return Ok(Value::Unit);
10500 },
10501 #[cfg(not(target_arch = "wasm32"))]
10502 "ui_scanlines" | "扫描线" | "走査線" | "스캔라인" | "เส้นสแกน" | "خطوط_اسکن" | "خطوط_المسح" | "קווי_סריקה" | "اسکین_لائنز" | "lignes_balayage_ui" | "ui_abtastzeilen" | "ui_линии_развёртки" =>
10503 {
10504 let x = self.arg_num(&args, 0, 0.)? as f32;
10505 let y = self.arg_num(&args, 1, 0.)? as f32;
10506 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10507 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10508 let dens = self.arg_num(&args, 4, 24.)? as usize;
10509 let th = self.ui_theme;
10510 let line = self.color_at(&args, 5, th.track);
10511 self.draw_ui(&ling_ui::widgets::scanlines(x, y, w0, h0, dens, line));
10512 return Ok(Value::Unit);
10513 },
10514
10515 #[cfg(not(target_arch = "wasm32"))]
10517 "ui_bar" | "进度条" | "バー" | "막대" | "แถบ" | "نوار_رابط" | "شريط_الواجهة" | "סרגל_ממשק" | "یو_آئی_بار" | "barre_ui" | "ui_leiste" | "ui_полоса" => {
10518 let x = self.arg_num(&args, 0, 0.)? as f32;
10519 let y = self.arg_num(&args, 1, 0.)? as f32;
10520 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10521 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10522 let val = self.arg_num(&args, 4, 0.)? as f32;
10523 let max = self.arg_num(&args, 5, 1.)? as f32;
10524 let th = self.ui_theme;
10525 let fill = self.color_at(&args, 6, th.primary);
10526 self.draw_ui(&ling_ui::widgets::bar(
10527 x,
10528 y,
10529 w0,
10530 h0,
10531 val / max.max(1e-6),
10532 fill,
10533 th.track,
10534 ));
10535 return Ok(Value::Unit);
10536 },
10537 #[cfg(not(target_arch = "wasm32"))]
10538 "ui_segbar" | "分段条" | "分割バー" | "분할막대" | "แถบแบ่ง" | "نوار_قطعهای" | "شريط_مقسم" | "סרגל_מקוטע" | "سیگمنٹ_بار" | "barre_segmentée_ui" | "ui_segmentleiste" | "ui_сегментная_полоса" =>
10539 {
10540 let x = self.arg_num(&args, 0, 0.)? as f32;
10541 let y = self.arg_num(&args, 1, 0.)? as f32;
10542 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10543 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10544 let val = self.arg_num(&args, 4, 0.)? as f32;
10545 let max = self.arg_num(&args, 5, 1.)? as f32;
10546 let segs = self.arg_num(&args, 6, 10.)? as usize;
10547 let th = self.ui_theme;
10548 let fill = self.color_at(&args, 7, th.primary);
10549 self.draw_ui(&ling_ui::widgets::segbar(
10550 x,
10551 y,
10552 w0,
10553 h0,
10554 val / max.max(1e-6),
10555 segs,
10556 fill,
10557 th.track,
10558 ));
10559 return Ok(Value::Unit);
10560 },
10561 #[cfg(not(target_arch = "wasm32"))]
10562 "ui_gauge" | "仪表" | "ゲージ" | "게이지" | "มาตรวัด" | "گیج_رابط" | "مقياس_الواجهة" | "מד_ממשק" | "یو_آئی_گیج" | "jauge_ui" | "ui_anzeige" | "ui_индикатор" => {
10563 let cx = self.arg_num(&args, 0, 0.)? as f32;
10564 let cy = self.arg_num(&args, 1, 0.)? as f32;
10565 let r = self.arg_num(&args, 2, 50.)? as f32;
10566 let val = self.arg_num(&args, 3, 0.)? as f32;
10567 let max = self.arg_num(&args, 4, 1.)? as f32;
10568 let th = self.ui_theme;
10569 let needle = self.color_at(&args, 5, th.warn);
10570 self.draw_ui(&ling_ui::widgets::gauge(
10571 cx,
10572 cy,
10573 r,
10574 val / max.max(1e-6),
10575 needle,
10576 th.accent,
10577 th.track,
10578 ));
10579 return Ok(Value::Unit);
10580 },
10581 #[cfg(not(target_arch = "wasm32"))]
10582 "ui_ring" | "环表" | "リングメーター" | "링미터" | "วงแหวนวัด" | "حلقه_گیج" | "حلقة_قياس" | "טבעת_מד" | "رنگ_گیج" | "anneau_ui" | "ui_кольцо" =>
10583 {
10584 let cx = self.arg_num(&args, 0, 0.)? as f32;
10585 let cy = self.arg_num(&args, 1, 0.)? as f32;
10586 let r = self.arg_num(&args, 2, 40.)? as f32;
10587 let val = self.arg_num(&args, 3, 0.)? as f32;
10588 let max = self.arg_num(&args, 4, 1.)? as f32;
10589 let th = self.ui_theme;
10590 let fill = self.color_at(&args, 5, th.primary);
10591 self.draw_ui(&ling_ui::widgets::ring(
10592 cx,
10593 cy,
10594 r,
10595 val / max.max(1e-6),
10596 fill,
10597 th.track,
10598 ));
10599 return Ok(Value::Unit);
10600 },
10601 #[cfg(not(target_arch = "wasm32"))]
10602 "ui_vu" | "音量条" | "VUメーター" | "음량막대" | "มาตรเสียง" | "گیج_صدا" | "مقياس_مستوى_الصوت" | "מד_עוצמה" | "وی_یو_میٹر" | "vumètre_ui" | "ui_vumeter" | "ui_вю_метр" =>
10603 {
10604 let x = self.arg_num(&args, 0, 0.)? as f32;
10605 let y = self.arg_num(&args, 1, 0.)? as f32;
10606 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10607 let h0 = self.arg_num(&args, 3, 60.)? as f32;
10608 let levels = self.arg_list_f32(&args, 4);
10609 let th = self.ui_theme;
10610 let fill = self.color_at(&args, 5, th.primary);
10611 self.draw_ui(&ling_ui::widgets::vu(x, y, w0, h0, &levels, fill, th.warn));
10612 return Ok(Value::Unit);
10613 },
10614 #[cfg(not(target_arch = "wasm32"))]
10615 "ui_spark" | "迷你图" | "スパークライン" | "스파크라인" | "กราฟจิ๋ว" | "نمودار_ریز" | "رسم_مصغر" | "גרף_זעיר" | "اسپارک_لائن" | "mini_graphe_ui" | "ui_sparkline" | "ui_мини_график" =>
10616 {
10617 let x = self.arg_num(&args, 0, 0.)? as f32;
10618 let y = self.arg_num(&args, 1, 0.)? as f32;
10619 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10620 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10621 let vals = self.arg_list_f32(&args, 4);
10622 let th = self.ui_theme;
10623 let line = self.color_at(&args, 5, th.accent);
10624 self.draw_ui(&ling_ui::widgets::spark(x, y, w0, h0, &vals, line));
10625 return Ok(Value::Unit);
10626 },
10627 #[cfg(not(target_arch = "wasm32"))]
10628 "ui_battery" | "电池" | "バッテリー" | "배터리" | "แบตเตอรี่" | "نشانگر_باتری" | "مؤشر_البطارية" | "מחוון_סוללה" | "بیٹری_انڈیکیٹر" | "batterie_ui" | "ui_batterie" | "ui_батарея" =>
10629 {
10630 let x = self.arg_num(&args, 0, 0.)? as f32;
10631 let y = self.arg_num(&args, 1, 0.)? as f32;
10632 let w0 = self.arg_num(&args, 2, 50.)? as f32;
10633 let h0 = self.arg_num(&args, 3, 22.)? as f32;
10634 let val = self.arg_num(&args, 4, 1.)? as f32;
10635 let max = self.arg_num(&args, 5, 1.)? as f32;
10636 let th = self.ui_theme;
10637 let fill = self.color_at(&args, 6, th.accent);
10638 self.draw_ui(&ling_ui::widgets::battery(
10639 x,
10640 y,
10641 w0,
10642 h0,
10643 val / max.max(1e-6),
10644 fill,
10645 th.track,
10646 th.warn,
10647 ));
10648 return Ok(Value::Unit);
10649 },
10650
10651 #[cfg(not(target_arch = "wasm32"))]
10653 "ui_button" | "按钮" | "ボタン" | "버튼" | "ปุ่ม" | "دکمه_رابط" | "زر_الواجهة" | "כפתור_ממשק" | "یو_آئی_بٹن" | "bouton_ui" | "ui_knopf" | "ui_кнопка" => {
10654 let x = self.arg_num(&args, 0, 0.)? as f32;
10655 let y = self.arg_num(&args, 1, 0.)? as f32;
10656 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10657 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10658 let (mx, my, down) = self.mouse_now();
10659 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10660 let clicked = hover && down && !self.mouse_was_down;
10661 let th = self.ui_theme;
10662 let prim = self.color_at(&args, 4, th.primary);
10663 self.draw_ui(&ling_ui::widgets::button(
10664 x,
10665 y,
10666 w0,
10667 h0,
10668 hover,
10669 down && hover,
10670 prim,
10671 th.bg,
10672 ));
10673 return Ok(Value::Number(if clicked { 1.0 } else { 0.0 }));
10674 },
10675 #[cfg(not(target_arch = "wasm32"))]
10676 "ui_toggle" | "开关" | "トグル" | "토글" | "สวิตช์" | "کلید_ضامن" | "مفتاح_تبديل" | "מתג" | "ٹوگل" | "bascule_ui" | "ui_schalter" | "ui_переключатель" => {
10677 let x = self.arg_num(&args, 0, 0.)? as f32;
10678 let y = self.arg_num(&args, 1, 0.)? as f32;
10679 let w0 = self.arg_num(&args, 2, 52.)? as f32;
10680 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10681 let mut state = self.arg_num(&args, 4, 0.)? > 0.5;
10682 let (mx, my, down) = self.mouse_now();
10683 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10684 if hover && down && !self.mouse_was_down {
10685 state = !state;
10686 }
10687 let th = self.ui_theme;
10688 let on = self.color_at(&args, 5, th.accent);
10689 self.draw_ui(&ling_ui::widgets::toggle(x, y, w0, h0, state, on, th.track));
10690 return Ok(Value::Number(if state { 1.0 } else { 0.0 }));
10691 },
10692 #[cfg(not(target_arch = "wasm32"))]
10693 "ui_slider" | "滑块" | "スライダー" | "슬라이더" | "แถบเลื่อน" | "لغزنده" | "شريط_انزلاق" | "מחוון_החלקה" | "سلائیڈر" | "curseur_ui" | "ui_schieberegler" | "ui_ползунок" =>
10694 {
10695 let x = self.arg_num(&args, 0, 0.)? as f32;
10696 let y = self.arg_num(&args, 1, 0.)? as f32;
10697 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10698 let mut val = self.arg_num(&args, 3, 0.)? as f32;
10699 let mn = self.arg_num(&args, 4, 0.)? as f32;
10700 let mx_ = self.arg_num(&args, 5, 1.)? as f32;
10701 let (mx, my, down) = self.mouse_now();
10702 let hover = ling_ui::holo::hit_rect(mx, my, x - 8.0, y - 10.0, w0 + 16.0, 20.0);
10703 if hover && down {
10704 let frac = ((mx - x) / w0).clamp(0.0, 1.0);
10705 val = mn + (mx_ - mn) * frac;
10706 }
10707 let frac = ((val - mn) / (mx_ - mn).abs().max(1e-6)).clamp(0.0, 1.0);
10708 let th = self.ui_theme;
10709 let fill = self.color_at(&args, 6, th.primary);
10710 self.draw_ui(&ling_ui::widgets::slider(
10711 x, y, w0, frac, hover, fill, th.track,
10712 ));
10713 return Ok(Value::Number(val as f64));
10714 },
10715 #[cfg(not(target_arch = "wasm32"))]
10716 "ui_checkbox" | "复选框" | "チェックボックス" | "체크박스" | "ช่องเลือก" | "جعبه_علامت" | "مربع_اختيار" | "תיבת_סימון" | "چیک_باکس" | "case_cocher_ui" | "ui_kontrollkästchen" | "ui_флажок" =>
10717 {
10718 let x = self.arg_num(&args, 0, 0.)? as f32;
10719 let y = self.arg_num(&args, 1, 0.)? as f32;
10720 let s = self.arg_num(&args, 2, 20.)? as f32;
10721 let mut checked = self.arg_num(&args, 3, 0.)? > 0.5;
10722 let (mx, my, down) = self.mouse_now();
10723 let hover = ling_ui::holo::hit_rect(mx, my, x, y, s, s);
10724 if hover && down && !self.mouse_was_down {
10725 checked = !checked;
10726 }
10727 let th = self.ui_theme;
10728 let prim = self.color_at(&args, 4, th.primary);
10729 self.draw_ui(&ling_ui::widgets::checkbox(
10730 x, y, s, checked, hover, prim, th.track,
10731 ));
10732 return Ok(Value::Number(if checked { 1.0 } else { 0.0 }));
10733 },
10734 #[cfg(not(target_arch = "wasm32"))]
10735 "ui_tabs" | "标签页" | "タブ" | "탭" | "แท็บ" | "برگهها" | "ألسنة_الواجهة" | "לשוניות" | "ٹیبز" | "onglets_ui" | "ui_reiter" | "ui_вкладки" => {
10736 let x = self.arg_num(&args, 0, 0.)? as f32;
10737 let y = self.arg_num(&args, 1, 0.)? as f32;
10738 let w0 = self.arg_num(&args, 2, 240.)? as f32;
10739 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10740 let count = self.arg_num(&args, 4, 3.)? as usize;
10741 let mut active = self.arg_num(&args, 5, 0.)? as i32;
10742 let (mx, my, down) = self.mouse_now();
10743 let mut hover = -1;
10744 if my >= y && my <= y + h0 && mx >= x && mx <= x + w0 && count > 0 {
10745 hover = (((mx - x) / (w0 / count as f32)) as i32)
10746 .max(0)
10747 .min(count as i32 - 1);
10748 if down && !self.mouse_was_down {
10749 active = hover;
10750 }
10751 }
10752 let th = self.ui_theme;
10753 let prim = self.color_at(&args, 6, th.primary);
10754 self.draw_ui(&ling_ui::widgets::tabs(
10755 x,
10756 y,
10757 w0,
10758 h0,
10759 count,
10760 active as usize,
10761 hover,
10762 prim,
10763 th.track,
10764 ));
10765 return Ok(Value::Number(active as f64));
10766 },
10767 #[cfg(not(target_arch = "wasm32"))]
10768 "ui_progress" | "进度" | "プログレス" | "진행바" | "ความคืบหน้า" | "نوار_پیشرفت" | "شريط_التقدم" | "פס_התקדמות" | "پیش_رفت_بار" | "progression_ui" | "ui_fortschritt" | "ui_прогресс" =>
10769 {
10770 let x = self.arg_num(&args, 0, 0.)? as f32;
10771 let y = self.arg_num(&args, 1, 0.)? as f32;
10772 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10773 let h0 = self.arg_num(&args, 3, 12.)? as f32;
10774 let frac = self.arg_num(&args, 4, 0.)? as f32;
10775 let th = self.ui_theme;
10776 let fill = self.color_at(&args, 5, th.accent);
10777 self.draw_ui(&ling_ui::widgets::progress(
10778 x, y, w0, h0, frac, fill, th.track,
10779 ));
10780 return Ok(Value::Unit);
10781 },
10782 #[cfg(not(target_arch = "wasm32"))]
10783 "ui_tooltip" | "提示框" | "ツールチップ" | "툴팁" | "คำแนะนำ" | "راهنمای_شناور" | "تلميح_الواجهة" | "חלונית_עזרה" | "ٹول_ٹپ" | "infobulle_ui" | "ui_подсказка" =>
10784 {
10785 let x = self.arg_num(&args, 0, 0.)? as f32;
10786 let y = self.arg_num(&args, 1, 0.)? as f32;
10787 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10788 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10789 let th = self.ui_theme;
10790 let prim = self.color_at(&args, 4, th.primary);
10791 self.draw_ui(&ling_ui::widgets::tooltip(x, y, w0, h0, prim, th.bg));
10792 return Ok(Value::Unit);
10793 },
10794 #[cfg(not(target_arch = "wasm32"))]
10795 "ui_stepper" | "步进器" | "ステッパー" | "스테퍼" | "ตัวปรับค่า" | "پلهگر" | "زر_خطوات" | "בורר_מדורג" | "اسٹیپر" | "pas_à_pas_ui" | "ui_schrittsteuerung" | "ui_степпер" =>
10796 {
10797 let x = self.arg_num(&args, 0, 0.)? as f32;
10798 let y = self.arg_num(&args, 1, 0.)? as f32;
10799 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10800 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10801 let mut val = self.arg_num(&args, 4, 0.)? as f32;
10802 let step = self.arg_num(&args, 5, 1.)? as f32;
10803 let (mx, my, down) = self.mouse_now();
10804 let hm = ling_ui::holo::hit_rect(mx, my, x, y, h0, h0);
10805 let hp = ling_ui::holo::hit_rect(mx, my, x + w0 - h0, y, h0, h0);
10806 if down && !self.mouse_was_down {
10807 if hm {
10808 val -= step;
10809 }
10810 if hp {
10811 val += step;
10812 }
10813 }
10814 let th = self.ui_theme;
10815 let prim = self.color_at(&args, 6, th.primary);
10816 self.draw_ui(&ling_ui::widgets::stepper(
10817 x, y, w0, h0, hm, hp, prim, th.track,
10818 ));
10819 return Ok(Value::Number(val as f64));
10820 },
10821
10822 #[cfg(not(target_arch = "wasm32"))]
10824 "ui_healthbar" | "血条" | "体力バー" | "체력바" | "แถบพลังชีวิต" | "نوار_سلامتی" | "شريط_الصحة" | "פס_בריאות" | "ہیلتھ_بار" | "barre_vie_ui" | "ui_lebensbalken" | "ui_полоса_здоровья" =>
10825 {
10826 let x = self.arg_num(&args, 0, 0.)? as f32;
10827 let y = self.arg_num(&args, 1, 0.)? as f32;
10828 let w0 = self.arg_num(&args, 2, 180.)? as f32;
10829 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10830 let val = self.arg_num(&args, 4, 1.)? as f32;
10831 let max = self.arg_num(&args, 5, 1.)? as f32;
10832 let pulse = self.arg_num(&args, 6, 0.)? as f32;
10833 let th = self.ui_theme;
10834 let full = self.color_at(&args, 7, th.accent);
10835 self.draw_ui(&ling_ui::widgets::healthbar(
10836 x,
10837 y,
10838 w0,
10839 h0,
10840 val / max.max(1e-6),
10841 pulse,
10842 full,
10843 th.warn,
10844 th.track,
10845 ));
10846 return Ok(Value::Unit);
10847 },
10848 #[cfg(not(target_arch = "wasm32"))]
10849 "ui_cooldown" | "冷却" | "クールダウン" | "쿨다운" | "คูลดาวน์" | "زمان_خنکسازی" | "مؤقت_التهدئة" | "זמן_קירור" | "کول_ڈاؤن" | "recharge_ui" | "ui_abklingzeit" | "ui_перезарядка" =>
10850 {
10851 let cx = self.arg_num(&args, 0, 0.)? as f32;
10852 let cy = self.arg_num(&args, 1, 0.)? as f32;
10853 let r = self.arg_num(&args, 2, 28.)? as f32;
10854 let frac = self.arg_num(&args, 3, 0.)? as f32;
10855 let th = self.ui_theme;
10856 let fill = self.color_at(&args, 4, th.primary);
10857 self.draw_ui(&ling_ui::widgets::cooldown(cx, cy, r, frac, fill, th.track));
10858 return Ok(Value::Unit);
10859 },
10860 #[cfg(not(target_arch = "wasm32"))]
10861 "ui_counter" | "计数器" | "カウンター" | "카운터" | "ตัวนับ" | "شمارشگر" | "عداد_الواجهة" | "מונה_ממשק" | "کاؤنٹر" | "compteur_ui" | "ui_zähler" | "ui_счётчик" => {
10862 let x = self.arg_num(&args, 0, 0.)? as f32;
10863 let y = self.arg_num(&args, 1, 0.)? as f32;
10864 let dw = self.arg_num(&args, 2, 14.)? as f32;
10865 let dh = self.arg_num(&args, 3, 24.)? as f32;
10866 let val = self.arg_num(&args, 4, 0.)? as i64;
10867 let digits = self.arg_num(&args, 5, 4.)? as usize;
10868 let th = self.ui_theme;
10869 let on = self.color_at(&args, 6, th.primary);
10870 let off = ling_ui::widgets::shade(th.track, 0.5);
10871 self.draw_ui(&ling_ui::widgets::counter(
10872 x, y, dw, dh, val, digits, on, off,
10873 ));
10874 return Ok(Value::Unit);
10875 },
10876 #[cfg(not(target_arch = "wasm32"))]
10877 "ui_minimap" | "小地图" | "ミニマップ" | "미니맵" | "แผนที่ย่อ" | "نقشه_کوچک" | "خريطة_مصغرة" | "מפה_מוקטנת" | "منی_میپ" | "minicarte_ui" | "ui_minikarte" | "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 w0 = self.arg_num(&args, 2, 140.)? as f32;
10882 let h0 = self.arg_num(&args, 3, 140.)? as f32;
10883 let th = self.ui_theme;
10884 let prim = self.color_at(&args, 4, th.primary);
10885 self.draw_ui(&ling_ui::widgets::minimap(x, y, w0, h0, prim, th.bg));
10886 return Ok(Value::Unit);
10887 },
10888 #[cfg(not(target_arch = "wasm32"))]
10889 "ui_dpad" | "方向键" | "方向パッド" | "방향패드" | "ปุ่มทิศทาง" | "دسته_جهتدار" | "لوحة_الاتجاهات" | "לוח_כיוונים" | "ڈی_پیڈ" | "croix_direction_ui" | "ui_steuerkreuz" | "ui_крестовина" =>
10890 {
10891 let cx = self.arg_num(&args, 0, 0.)? as f32;
10892 let cy = self.arg_num(&args, 1, 0.)? as f32;
10893 let r = self.arg_num(&args, 2, 50.)? as f32;
10894 let (mx, my, down) = self.mouse_now();
10895 let mut dir = 0;
10896 if down {
10897 let (dx, dy) = (mx - cx, my - cy);
10898 if dx * dx + dy * dy <= r * r {
10899 if dx.abs() > dy.abs() {
10900 dir = if dx > 0.0 { 2 } else { 4 };
10901 } else {
10902 dir = if dy > 0.0 { 3 } else { 1 };
10903 }
10904 }
10905 }
10906 let th = self.ui_theme;
10907 let prim = self.color_at(&args, 3, th.primary);
10908 self.draw_ui(&ling_ui::widgets::dpad(cx, cy, r, dir, prim, th.track));
10909 return Ok(Value::Number(dir as f64));
10910 },
10911 #[cfg(not(target_arch = "wasm32"))]
10912 "ui_slotgrid" | "物品格" | "スロットグリッド" | "슬롯격자" | "ช่องไอเทม" | "شبکه_شیار" | "شبكة_الفتحات" | "רשת_חריצים" | "سلاٹ_گرڈ" | "grille_emplacements_ui" | "ui_slotraster" | "ui_сетка_слотов" =>
10913 {
10914 let x = self.arg_num(&args, 0, 0.)? as f32;
10915 let y = self.arg_num(&args, 1, 0.)? as f32;
10916 let cols = self.arg_num(&args, 2, 4.)? as usize;
10917 let rows = self.arg_num(&args, 3, 1.)? as usize;
10918 let cell = self.arg_num(&args, 4, 36.)? as f32;
10919 let sel = self.arg_num(&args, 5, -1.)? as i32;
10920 let th = self.ui_theme;
10921 let prim = self.color_at(&args, 6, th.primary);
10922 self.draw_ui(&ling_ui::widgets::slotgrid(
10923 x, y, cols, rows, cell, sel, prim, th.track,
10924 ));
10925 return Ok(Value::Unit);
10926 },
10927 #[cfg(not(target_arch = "wasm32"))]
10928 "ui_vignette" | "暗角" | "ビネット" | "비네트" | "ขอบมืด" | "سایهگرد_کادر" | "تظليل_الحواف" | "הצללת_מסגרת" | "ویگنیٹ" | "vignette_ui" | "ui_виньетка" => {
10929 let intensity = self.arg_num(&args, 0, 0.5)? as f32;
10930 let (w, h) = {
10931 let g = self.gfx.borrow();
10932 (g.width as f32, g.height as f32)
10933 };
10934 let th = self.ui_theme;
10935 let col = self.color_at(&args, 1, th.warn);
10936 self.draw_ui(&ling_ui::widgets::vignette(w, h, intensity, col));
10937 return Ok(Value::Unit);
10938 },
10939
10940 #[cfg(not(target_arch = "wasm32"))]
10942 "ui_gauge3d" | "立体仪表" | "立体ゲージ" | "입체게이지" | "มาตรวัด3มิติ" | "گیج_سهبعدی" | "مقياس_ثلاثي_الأبعاد" | "מד_תלת_ממדי" | "تھری_ڈی_گیج" | "jauge_3d_ui" | "ui_anzeige_3d" | "ui_индикатор_3d" =>
10943 {
10944 let cx = self.arg_num(&args, 0, 0.)? as f32;
10945 let cy = self.arg_num(&args, 1, 0.)? as f32;
10946 let r = self.arg_num(&args, 2, 50.)? as f32;
10947 let val = self.arg_num(&args, 3, 0.)? as f32;
10948 let max = self.arg_num(&args, 4, 1.)? as f32;
10949 let spin = self.arg_num(&args, 5, 0.)? as f32;
10950 let th = self.ui_theme;
10951 let fill = self.color_at(&args, 6, th.primary);
10952 self.draw_ui(&ling_ui::widgets::gauge3d(
10953 cx,
10954 cy,
10955 r,
10956 val / max.max(1e-6),
10957 spin,
10958 fill,
10959 th.track,
10960 ));
10961 return Ok(Value::Unit);
10962 },
10963 #[cfg(not(target_arch = "wasm32"))]
10964 "ui_panel3d" | "立体面板" | "立体パネル" | "입체패널" | "แผง3มิติ" | "پنل_سهبعدی" | "لوحة_ثلاثية_الأبعاد" | "לוח_תלת_ממדי" | "تھری_ڈی_پینل" | "panneau_3d_ui" | "ui_feld_3d" | "ui_панель_3d" =>
10965 {
10966 let x = self.arg_num(&args, 0, 0.)? as f32;
10967 let y = self.arg_num(&args, 1, 0.)? as f32;
10968 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10969 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10970 let depth = self.arg_num(&args, 4, 14.)? as f32;
10971 let th = self.ui_theme;
10972 let prim = self.color_at(&args, 5, th.primary);
10973 self.draw_ui(&ling_ui::widgets::panel3d(x, y, w0, h0, depth, prim, th.bg));
10974 return Ok(Value::Unit);
10975 },
10976 #[cfg(not(target_arch = "wasm32"))]
10977 "ui_radar3d" | "立体雷达" | "立体レーダー" | "입체레이더" | "เรดาร์3มิติ" | "رادار_سهبعدی" | "رادار_ثلاثي_الأبعاد" | "מכ״ם_תלת_ממדי" | "تھری_ڈی_ریڈار" | "radar_3d_ui" | "ui_radar_3d" | "ui_радар_3d" =>
10978 {
10979 let cx = self.arg_num(&args, 0, 0.)? as f32;
10980 let cy = self.arg_num(&args, 1, 0.)? as f32;
10981 let r = self.arg_num(&args, 2, 60.)? as f32;
10982 let tilt = self.arg_num(&args, 3, 0.9)? as f32;
10983 let sweep = self.arg_num(&args, 4, 0.)? as f32;
10984 let th = self.ui_theme;
10985 let prim = self.color_at(&args, 5, th.primary);
10986 self.draw_ui(&ling_ui::widgets::radar3d(
10987 cx, cy, r, tilt, sweep, prim, th.track,
10988 ));
10989 return Ok(Value::Unit);
10990 },
10991
10992 #[cfg(not(target_arch = "wasm32"))]
10994 "audio_blip" | "提示音" | "ビープ音" | "효과음" | "เสียงบี๊บ" | "بوق_کوتاه" | "نغمة_قصيرة" | "ביפ" | "بلپ_آواز" | "bip_audio" | "звук_бип" =>
10995 {
10996 let freq = self.arg_num(&args, 0, 660.)? as f32;
10997 let dur = self.arg_num(&args, 1, 0.08)? as f32;
10998 let wave = Wave::from_name(&self.arg_str(&args, 2, "sine"));
10999 let amp = self.arg_num(&args, 3, 0.25)? as f32;
11000 if let Some(audio) = &self.audio {
11001 audio.blip(freq, amp, dur, wave);
11002 }
11003 return Ok(Value::Unit);
11004 },
11005 #[cfg(not(target_arch = "wasm32"))]
11006 "ui_sound" | "界面音" | "UI音" | "인터페이스음" | "เสียงปุ่ม" | "صدای_رابط" | "صوت_الواجهة" | "צליל_ממשק" | "یو_آئی_آواز" | "son_ui" | "ui_klang" | "ui_звук" =>
11007 {
11008 let name = self.arg_str(&args, 0, "click");
11009 if let Some(audio) = &self.audio {
11010 match name.as_str() {
11011 "hover" => audio.blip(880.0, 0.10, 0.04, Wave::Sine),
11012 "confirm" => {
11013 audio.blip(660.0, 0.22, 0.07, Wave::Square);
11014 audio.blip(990.0, 0.18, 0.10, Wave::Square);
11015 },
11016 "error" => {
11017 audio.blip(180.0, 0.30, 0.16, Wave::Saw);
11018 audio.blip(140.0, 0.30, 0.18, Wave::Saw);
11019 },
11020 "toggle" => audio.blip(520.0, 0.22, 0.05, Wave::Triangle),
11021 "tick" => audio.blip(1500.0, 0.12, 0.02, Wave::Square),
11022 _ => audio.blip(720.0, 0.26, 0.05, Wave::Square), }
11024 }
11025 return Ok(Value::Unit);
11026 },
11027
11028 #[cfg(not(target_arch = "wasm32"))]
11036 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" | "charger_musique" | "musik_laden" | "загрузить_музыку" =>
11037 {
11038 let path = self.arg_str(&args, 0, "");
11039 let resolved = if std::path::Path::new(&path).exists() {
11040 path.clone()
11041 } else if let Some(d) = &self.source_dir {
11042 d.join(&path).to_string_lossy().into_owned()
11043 } else {
11044 path.clone()
11045 };
11046 match ling_music::load(&resolved) {
11047 Ok(t) => {
11048 let id = self.tracks.len();
11049 self.tracks.push(t);
11050 return Ok(Value::Number(id as f64));
11051 },
11052 Err(e) => {
11053 eprintln!("music_load failed ({path}): {e}");
11054 return Ok(Value::Number(-1.0));
11055 },
11056 }
11057 },
11058 #[cfg(not(target_arch = "wasm32"))]
11059 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" | "durée_musique" | "musik_dauer" | "длительность_музыки" =>
11060 {
11061 let id = self.arg_num(&args, 0, 0.0)? as i64;
11062 let d = self
11063 .tracks
11064 .get(id as usize)
11065 .map(|t| t.duration)
11066 .unwrap_or(0.0);
11067 return Ok(Value::Number(d as f64));
11068 },
11069 #[cfg(not(target_arch = "wasm32"))]
11070 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" | "ضربان_در_دقیقه" | "نبضات_بالدقيقة" | "פעימות_לדקה" | "بی_پی_ایم" | "bpm_musique" | "musik_bpm" | "музыка_bpm" =>
11071 {
11072 let id = self.arg_num(&args, 0, 0.0)? as i64;
11073 let b = self
11074 .tracks
11075 .get(id as usize)
11076 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
11077 .unwrap_or(0.0);
11078 return Ok(Value::Number(b as f64));
11079 },
11080 #[cfg(not(target_arch = "wasm32"))]
11081 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" | "tonalité_musique" | "musik_tonart" | "тональность_музыки" => {
11082 let id = self.arg_num(&args, 0, 0.0)? as i64;
11083 let k = self
11084 .tracks
11085 .get(id as usize)
11086 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
11087 .unwrap_or_default();
11088 return Ok(Value::Str(k));
11089 },
11090 #[cfg(not(target_arch = "wasm32"))]
11091 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" | "attaques_musique" | "musik_einsätze" | "атаки_музыки" =>
11092 {
11093 let id = self.arg_num(&args, 0, 0.0)? as i64;
11094 let v = self
11095 .tracks
11096 .get(id as usize)
11097 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
11098 .unwrap_or_default();
11099 return Ok(Value::List(Rc::new(
11100 v.into_iter().map(|x| Value::Number(x as f64)).collect(),
11101 )));
11102 },
11103 #[cfg(not(target_arch = "wasm32"))]
11104 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" | "شبکه_ضرب" | "شبكة_الإيقاع" | "רשת_פעימות" | "بیٹ_گرڈ" | "grille_temps_musique" | "musik_taktraster" | "сетка_ритма_музыки" =>
11105 {
11106 let id = self.arg_num(&args, 0, 0.0)? as i64;
11107 let beats = self
11108 .tracks
11109 .get(id as usize)
11110 .map(|t| {
11111 let b = ling_music::analysis::bpm(&t.mono, t.rate);
11112 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
11113 })
11114 .unwrap_or_default();
11115 return Ok(Value::List(Rc::new(
11116 beats.into_iter().map(|x| Value::Number(x as f64)).collect(),
11117 )));
11118 },
11119
11120 #[cfg(not(target_arch = "wasm32"))]
11122 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" | "jouer_musique" | "musik_abspielen" | "играть_музыку" =>
11123 {
11124 let id = self.arg_num(&args, 0, 0.0)? as i64;
11125 if self.ensure_music() {
11126 let track = self
11127 .tracks
11128 .get(id as usize)
11129 .map(|t| (t.stereo.clone(), t.rate));
11130 if let (Some((st, rate)), Some(m)) = (track, &self.music) {
11131 m.set_track(st, rate);
11132 m.play();
11133 } else if let Some(m) = &self.music {
11134 m.play();
11135 }
11136 }
11137 return Ok(Value::Unit);
11138 },
11139 #[cfg(not(target_arch = "wasm32"))]
11140 "music_pause" | "暂停音乐" | "音楽一時停止" | "음악일시정지" | "หยุดเพลงชั่วคราว" | "مکث_موسیقی" | "ألبث_الموسيقى" | "השהה_מוזיקה" | "موسیقی_روکو_مؤقت" | "pause_musique" | "musik_pausieren" | "пауза_музыки" =>
11141 {
11142 if let Some(m) = &self.music {
11143 m.pause();
11144 }
11145 return Ok(Value::Unit);
11146 },
11147 #[cfg(not(target_arch = "wasm32"))]
11148 "music_stop" | "停止音乐" | "音楽停止" | "음악정지" | "หยุดเพลง" | "توقف_موسیقی" | "أوقف_الموسيقى" | "עצור_מוזיקה" | "موسیقی_روکو" | "arrêter_musique" | "musik_stoppen" | "остановить_музыку" =>
11149 {
11150 if let Some(m) = &self.music {
11151 m.stop();
11152 }
11153 return Ok(Value::Unit);
11154 },
11155 #[cfg(not(target_arch = "wasm32"))]
11156 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" | "جستجوی_موسیقی" | "ابحث_في_الموسيقى" | "חפש_במוזיקה" | "موسیقی_تلاش" | "chercher_musique" | "musik_suchen" | "перемотать_музыку" =>
11157 {
11158 let sec = self.arg_num(&args, 0, 0.0)? as f32;
11159 if let Some(m) = &self.music {
11160 m.seek(sec);
11161 }
11162 return Ok(Value::Unit);
11163 },
11164 #[cfg(not(target_arch = "wasm32"))]
11165 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" | "موقعیت_موسیقی" | "موضع_الموسيقى" | "מיקום_מוזיקה" | "موسیقی_مقام" | "position_musique" | "musik_position" | "позиция_музыки" =>
11166 {
11167 let p = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
11168 return Ok(Value::Number(p as f64));
11169 },
11170 #[cfg(not(target_arch = "wasm32"))]
11171 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" | "بلندی_موسیقی" | "مستوى_الموسيقى" | "עוצמת_מוזיקה" | "موسیقی_شدت" | "volume_musique" | "musik_lautstärke" | "громкость_музыки" =>
11172 {
11173 let v = self.arg_num(&args, 0, 0.8)? as f32;
11174 if self.ensure_music() {
11175 if let Some(m) = &self.music {
11176 m.set_volume(v);
11177 }
11178 }
11179 return Ok(Value::Unit);
11180 },
11181
11182 #[cfg(not(target_arch = "wasm32"))]
11184 "music_patch" | "乐器音色" | "音色読込" | "악기패치" | "แพตช์เครื่องดนตรี" | "پچ_موسیقی" | "آلة_الموسيقى" | "תיקון_כלי_נגינה" | "میوزک_پیچ" | "patch_musique" | "musik_patch" | "патч_музыки" =>
11185 {
11186 let path = self.arg_str(&args, 0, "");
11187 let resolved = if std::path::Path::new(&path).exists() {
11188 path.clone()
11189 } else if let Some(d) = &self.source_dir {
11190 d.join(&path).to_string_lossy().into_owned()
11191 } else {
11192 path.clone()
11193 };
11194 if !self.ensure_music() {
11195 return Ok(Value::Number(-1.0));
11196 }
11197 match ling_music::patch::from_path(&resolved) {
11198 Ok(p) => {
11199 let id = self.music.as_ref().unwrap().add_patch(p);
11200 return Ok(Value::Number(id as f64));
11201 },
11202 Err(e) => {
11203 eprintln!("music_patch failed ({path}): {e}");
11204 return Ok(Value::Number(-1.0));
11205 },
11206 }
11207 },
11208 #[cfg(not(target_arch = "wasm32"))]
11209 "music_note" | "弹音符" | "音符演奏" | "음표연주" | "เล่นโน้ต" | "نواختن_نت" | "عزف_نغمة" | "נגן_תו" | "نوٹ_بجاؤ" | "note_musique" | "musik_note" | "нота_музыки" =>
11210 {
11211 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11212 let midi = self.pitch_arg(&args, 1, 60);
11213 let dur = self.arg_num(&args, 2, 0.5)? as f32;
11214 let vel = self.arg_num(&args, 3, 0.9)? as f32;
11215 if self.ensure_music() {
11216 if let Some(m) = &self.music {
11217 m.note(inst, midi, vel, dur);
11218 }
11219 }
11220 return Ok(Value::Unit);
11221 },
11222 #[cfg(not(target_arch = "wasm32"))]
11223 "music_note_on" | "音符开始" | "音符オン" | "음표켜기" | "โน้ตเริ่ม" | "شروع_نت" | "بدء_النغمة" | "התחלת_תו" | "نوٹ_شروع" | "note_musique_on" | "musik_note_an" | "нота_музыки_вкл" =>
11224 {
11225 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11226 let midi = self.pitch_arg(&args, 1, 60);
11227 let vel = self.arg_num(&args, 2, 0.9)? as f32;
11228 if self.ensure_music() {
11229 if let Some(m) = &self.music {
11230 m.note_on(inst, midi, vel);
11231 }
11232 }
11233 return Ok(Value::Unit);
11234 },
11235 #[cfg(not(target_arch = "wasm32"))]
11236 "music_note_off" | "音符结束" | "音符オフ" | "음표끄기" | "โน้ตจบ" | "پایان_نت" | "إيقاف_النغمة" | "סיום_תו" | "نوٹ_ختم" | "note_musique_off" | "musik_note_aus" | "нота_музыки_выкл" =>
11237 {
11238 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11239 let midi = self.pitch_arg(&args, 1, 60);
11240 if let Some(m) = &self.music {
11241 m.note_off(inst, midi);
11242 }
11243 return Ok(Value::Unit);
11244 },
11245
11246 #[cfg(not(target_arch = "wasm32"))]
11248 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" | "داوری_ضرب" | "حكم_الإيقاع" | "שיפוט_קצב" | "بیٹ_فیصلہ" | "juger_musique" | "musik_bewerten" | "оценить_музыку" =>
11249 {
11250 let delta_ms = self.arg_num(&args, 0, 9999.0)? as f32;
11251 return Ok(Value::Number(
11252 ling_music::Grade::judge(delta_ms).index() as f64
11253 ));
11254 },
11255 #[cfg(not(target_arch = "wasm32"))]
11256 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" | "نام_رتبه" | "اسم_التقييم" | "שם_דירוג" | "گریڈ_نام" | "nom_grade_musique" | "musik_bewertungsname" | "имя_оценки_музыки" =>
11257 {
11258 let idx = self.arg_num(&args, 0, 4.0)? as i32;
11259 return Ok(Value::Str(
11260 ling_music::Grade::from_index(idx).name().to_string(),
11261 ));
11262 },
11263
11264 #[cfg(not(target_arch = "wasm32"))]
11266 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" | "بارگذاری_متن_ترانه" | "تحميل_كلمات_الأغنية" | "טעינת_מילות_שיר" | "گیت_متن_لوڈ" | "lrc_musique" | "musik_lrc" | "lrc_музыки" =>
11267 {
11268 let path = self.arg_str(&args, 0, "");
11269 let resolved = if std::path::Path::new(&path).exists() {
11270 path.clone()
11271 } else if let Some(d) = &self.source_dir {
11272 d.join(&path).to_string_lossy().into_owned()
11273 } else {
11274 path.clone()
11275 };
11276 match std::fs::read_to_string(&resolved) {
11277 Ok(text) => {
11278 let id = self.lyrics.len();
11279 self.lyrics.push(ling_music::Lyrics::parse(&text));
11280 return Ok(Value::Number(id as f64));
11281 },
11282 Err(e) => {
11283 eprintln!("music_lrc failed ({path}): {e}");
11284 return Ok(Value::Number(-1.0));
11285 },
11286 }
11287 },
11288 #[cfg(not(target_arch = "wasm32"))]
11289 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" | "متن_ترانه_فعلی" | "كلمات_الأغنية_الحالية" | "מילות_שיר_נוכחיות" | "موجودہ_گیت_متن" | "paroles_musique" | "musik_liedtext" | "текст_песни" =>
11290 {
11291 let id = self.arg_num(&args, 0, 0.0)? as i64;
11292 let t = self.arg_num(&args, 1, 0.0)? as f32;
11293 let line = self
11294 .lyrics
11295 .get(id as usize)
11296 .map(|l| l.line_at(t).to_string())
11297 .unwrap_or_default();
11298 return Ok(Value::Str(line));
11299 },
11300 #[cfg(not(target_arch = "wasm32"))]
11301 "music_mic_pitch" | "麦克风音高" | "マイク音程" | "마이크음정" | "ระดับเสียงไมค์" | "زیروبمی_میکروفون" | "طبقة_صوت_الميكروفون" | "גובה_צליל_מיקרופון" | "مائیکروفون_پچ" | "hauteur_micro_musique" | "musik_mikrofon_tonhöhe" | "высота_тона_микрофона" =>
11302 {
11303 let hz = if let Some(mic) = self.mic.as_ref() {
11304 let s = mic.latest_samples();
11305 let rate = mic.sample_rate();
11306 ling_music::pitch::detect(&s, rate).unwrap_or(0.0)
11307 } else {
11308 0.0
11309 };
11310 return Ok(Value::Number(hz as f64));
11311 },
11312 #[cfg(not(target_arch = "wasm32"))]
11313 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" | "نام_نت" | "اسم_النغمة" | "שם_תו" | "نوٹ_نام" | "nom_note_musique" | "musik_notenname" | "имя_ноты_музыки" =>
11314 {
11315 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11316 return Ok(Value::Str(ling_music::note::hz_to_name(hz)));
11317 },
11318 #[cfg(not(target_arch = "wasm32"))]
11319 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" | "فرکانس_نت" | "تردد_النغمة" | "תדר_תו" | "نوٹ_ہرٹز" | "hz_musique" | "musik_hz" | "музыка_гц" =>
11320 {
11321 let midi = self.pitch_arg(&args, 0, 69);
11322 return Ok(Value::Number(
11323 ling_music::note::midi_to_hz(midi as f32) as f64
11324 ));
11325 },
11326 #[cfg(not(target_arch = "wasm32"))]
11327 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" | "امتیاز_زیروبمی" | "درجة_طبقة_الصوت" | "ציון_גובה_צליל" | "پچ_اسکور" | "score_hauteur_musique" | "musik_tonhöhen_punktzahl" | "счёт_высоты_тона" =>
11328 {
11329 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11330 let target = self.arg_num(&args, 1, 0.0)? as f32;
11331 return Ok(Value::Number(
11332 ling_music::karaoke::pitch_score(hz, target) as f64
11333 ));
11334 },
11335
11336 #[cfg(not(target_arch = "wasm32"))]
11338 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" | "بارگذاری_MIDI" | "تحميل_MIDI" | "טעינת_MIDI" | "MIDI_لوڈ" | "charger_midi_musique" | "musik_midi_laden" | "загрузить_midi_музыки" =>
11339 {
11340 let path = self.arg_str(&args, 0, "");
11341 let resolved = if std::path::Path::new(&path).exists() {
11342 path.clone()
11343 } else if let Some(d) = &self.source_dir {
11344 d.join(&path).to_string_lossy().into_owned()
11345 } else {
11346 path.clone()
11347 };
11348 match ling_music::midi::load(&resolved) {
11349 Ok(m) => {
11350 let id = self.midis.len();
11351 self.midis.push(m);
11352 return Ok(Value::Number(id as f64));
11353 },
11354 Err(e) => {
11355 eprintln!("music_midi_load failed ({path}): {e}");
11356 return Ok(Value::Number(-1.0));
11357 },
11358 }
11359 },
11360 #[cfg(not(target_arch = "wasm32"))]
11361 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" | "تعداد_MIDI" | "عدد_MIDI" | "מספר_MIDI" | "MIDI_تعداد" | "nombre_midi_musique" | "musik_midi_anzahl" | "число_midi_музыки" =>
11362 {
11363 let id = self.arg_num(&args, 0, 0.0)? as i64;
11364 let n = self
11365 .midis
11366 .get(id as usize)
11367 .map(|m| m.notes.len())
11368 .unwrap_or(0);
11369 return Ok(Value::Number(n as f64));
11370 },
11371 #[cfg(not(target_arch = "wasm32"))]
11373 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" | "نتهای_MIDI" | "نغمات_MIDI" | "תווי_MIDI" | "MIDI_نوٹس" | "notes_midi_musique" | "musik_midi_noten" | "ноты_midi_музыки" =>
11374 {
11375 let id = self.arg_num(&args, 0, 0.0)? as i64;
11376 let mut out = Vec::new();
11377 if let Some(m) = self.midis.get(id as usize) {
11378 for n in &m.notes {
11379 out.push(Value::Number(n.time as f64));
11380 out.push(Value::Number(n.midi as f64));
11381 }
11382 }
11383 return Ok(Value::List(Rc::new(out)));
11384 },
11385 #[cfg(not(target_arch = "wasm32"))]
11387 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" | "میلههای_MIDI" | "أعمدة_MIDI" | "עמודות_MIDI" | "MIDI_بارز" | "mesures_midi_musique" | "musik_midi_takte" | "такты_midi_музыки" =>
11388 {
11389 let id = self.arg_num(&args, 0, 0.0)? as i64;
11390 let mut out = Vec::new();
11391 if let Some(m) = self.midis.get(id as usize) {
11392 for n in &m.notes {
11393 out.push(Value::Number(n.time as f64));
11394 out.push(Value::Number(n.midi as f64));
11395 out.push(Value::Number(n.dur as f64));
11396 }
11397 }
11398 return Ok(Value::List(Rc::new(out)));
11399 },
11400
11401 #[cfg(not(target_arch = "wasm32"))]
11403 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" | "طیف_موسیقی" | "طيف_الموسيقى" | "ספקטרום_מוזיקה" | "میوزک_اسپیکٹرم" | "fft_musique" | "musik_fft" | "fft_музыки" =>
11404 {
11405 let id = self.arg_num(&args, 0, 0.0)? as i64;
11406 let nbands = self.arg_num(&args, 1, 16.0)? as usize;
11407 let pos = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
11408 if let Some(t) = self.tracks.get(id as usize) {
11409 let idx = (pos * t.rate as f32) as usize;
11410 let end = (idx + 2048).min(t.mono.len());
11411 if end > idx + 64 {
11412 self.fft.borrow_mut().push_samples(&t.mono[idx..end]);
11413 }
11414 }
11415 let bands = self.fft.borrow().freq_bands(nbands);
11416 return Ok(Value::List(Rc::new(
11417 bands.into_iter().map(|x| Value::Number(x as f64)).collect(),
11418 )));
11419 },
11420
11421 #[cfg(not(target_arch = "wasm32"))]
11423 "audio_stop_sfx" | "停止音效" | "効果音停止" | "효과음정지" | "หยุดเอฟเฟกต์ทั้งหมด" | "توقف_همه_جلوهها" | "أوقف_كل_المؤثرات" | "עצור_כל_האפקטים" | "تمام_ایفیکٹ_روکو" =>
11424 {
11425 if let Some(a) = &self.audio {
11426 a.stop_all_sfx();
11427 }
11428 return Ok(Value::Unit);
11429 },
11430 #[cfg(not(target_arch = "wasm32"))]
11432 "audio_sfx" | "音效" | "空間効果音" | "공간효과음" | "เสียงเอฟเฟกต์" | "جلوه_صوتی" | "مؤثرات_صوتية" | "אפקט_קול" | "آواز_ایفیکٹ" | "effet_sonore" | "klangeffekt" | "звуковой_эффект" =>
11433 {
11434 let x = self.arg_num(&args, 0, 0.0)? as f32;
11435 let y = self.arg_num(&args, 1, 0.0)? as f32;
11436 let z = self.arg_num(&args, 2, 0.0)? as f32;
11437 let w = self.arg_num(&args, 3, 1.0)? as f32;
11438 let freq = self.arg_num(&args, 4, 440.0)? as f32;
11439 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11440 let dur = self.arg_num(&args, 6, 0.15)? as f32;
11441 let wave = Wave::from_name(&self.arg_str(&args, 7, "sine"));
11442 if let Some(a) = &self.audio {
11443 a.sfx(x, y, z, w, freq, amp, dur, wave);
11444 }
11445 return Ok(Value::Unit);
11446 },
11447 #[cfg(not(target_arch = "wasm32"))]
11452 "morph_note" | "โน้ตมอร์ฟ" | "变形音" | "モーフ音" | "모프음" | "نت_مورف" | "نغمة_متحولة" | "תו_מורף" | "مورف_نوٹ" =>
11453 {
11454 let x = self.arg_num(&args, 0, 0.0)? as f32;
11455 let y = self.arg_num(&args, 1, 0.0)? as f32;
11456 let z = self.arg_num(&args, 2, 0.0)? as f32;
11457 let w = self.arg_num(&args, 3, 1.0)? as f32;
11458 let freq = self.arg_num(&args, 4, 220.0)? as f32;
11459 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11460 let dur = self.arg_num(&args, 6, 0.6)? as f32;
11461 let material = self.arg_num(&args, 7, 0.0)?.clamp(0.0, 3.0) as u8;
11462 let morph = self.arg_num(&args, 8, 0.0)? as f32;
11463 if let Some(a) = &self.audio {
11464 a.morph_note(x, y, z, w, freq, amp, dur, material, morph);
11465 }
11466 return Ok(Value::Unit);
11467 },
11468 #[cfg(not(target_arch = "wasm32"))]
11470 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" | "بارگذاری_نمونه_صدا" | "تحميل_عينة_صوتية" | "טעינת_דגימת_קול" | "آواز_نمونہ_لوڈ" | "charger_échantillon" | "sample_laden" | "загрузить_семпл" =>
11471 {
11472 let path = self.arg_str(&args, 0, "");
11473 let resolved = if std::path::Path::new(&path).exists() {
11474 path.clone()
11475 } else if let Some(d) = &self.source_dir {
11476 d.join(&path).to_string_lossy().into_owned()
11477 } else {
11478 path.clone()
11479 };
11480 match ling_music::load(&resolved) {
11481 Ok(t) => {
11482 if let Some(a) = &self.audio {
11483 return Ok(Value::Number(a.add_sample(t.mono, t.rate) as f64));
11484 }
11485 return Ok(Value::Number(-1.0));
11486 },
11487 Err(e) => {
11488 eprintln!("audio_sample_load failed ({path}): {e}");
11489 return Ok(Value::Number(-1.0));
11490 },
11491 }
11492 },
11493 #[cfg(not(target_arch = "wasm32"))]
11494 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" | "پخش_نمونه_صدا" | "تشغيل_عينة_صوتية" | "נגינת_דגימת_קול" | "آواز_نمونہ_چلاؤ" | "jouer_échantillon" | "sample_abspielen" | "играть_семпл" =>
11495 {
11496 let id = self.arg_num(&args, 0, 0.0)? as usize;
11497 let x = self.arg_num(&args, 1, 0.0)? as f32;
11498 let y = self.arg_num(&args, 2, 0.0)? as f32;
11499 let z = self.arg_num(&args, 3, 0.0)? as f32;
11500 let w = self.arg_num(&args, 4, 1.0)? as f32;
11501 let vol = self.arg_num(&args, 5, 1.0)? as f32;
11502 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
11503 let v = self
11504 .audio
11505 .as_ref()
11506 .map(|a| a.play_sample(id, x, y, z, w, vol, looping))
11507 .unwrap_or(0);
11508 return Ok(Value::Number(v as f64));
11509 },
11510 #[cfg(not(target_arch = "wasm32"))]
11511 "audio_sample_stop" | "停止采样" | "サンプル停止" | "샘플정지" | "หยุดตัวอย่างเสียง" | "توقف_نمونه_صدا" | "إيقاف_عينة_صوتية" | "עצירת_דגימת_קול" | "آواز_نمونہ_روکو" | "arrêter_échantillon" | "sample_stoppen" | "остановить_семпл" =>
11512 {
11513 let v = self.arg_num(&args, 0, 0.0)? as u32;
11514 if let Some(a) = &self.audio {
11515 a.stop_sample(v);
11516 }
11517 return Ok(Value::Unit);
11518 },
11519 #[cfg(not(target_arch = "wasm32"))]
11521 "audio_fx_delay" | "回声" | "ディレイ効果" | "딜레이" | "เสียงสะท้อน" | "افکت_تاخیر" | "صدى_تأخير" | "אפקט_עיכוב" | "تاخیر_ایفیکٹ" | "délai_audio" | "audio_verzögerung" | "звук_задержка" =>
11522 {
11523 let time = self.arg_num(&args, 0, 0.3)? as f32;
11524 let fb = self.arg_num(&args, 1, 0.3)? as f32;
11525 let mix = self.arg_num(&args, 2, 0.3)? as f32;
11526 if let Some(a) = &self.audio {
11527 a.fx_delay(time, fb, mix);
11528 }
11529 return Ok(Value::Unit);
11530 },
11531 #[cfg(not(target_arch = "wasm32"))]
11532 "audio_fx_reverb" | "混响" | "リバーブ" | "리버브" | "เสียงก้อง" | "افکت_پژواک" | "صدى_ارتداد" | "אפקט_הדהוד" | "بازگشت_آواز_ایفیکٹ" | "réverbération_audio" | "audio_nachhall" | "звук_реверберация" =>
11533 {
11534 let mix = self.arg_num(&args, 0, 0.3)? as f32;
11535 if let Some(a) = &self.audio {
11536 a.fx_reverb(mix);
11537 }
11538 return Ok(Value::Unit);
11539 },
11540 #[cfg(not(target_arch = "wasm32"))]
11541 "audio_fx_lowpass" | "低通滤波" | "ローパス" | "저역통과" | "กรองความถี่ต่ำ" | "فیلتر_پایینگذر" | "مرشح_تمرير_منخفض" | "מסנן_תדר_נמוך" | "لو_پاس_فلٹر" | "passe_bas_audio" | "audio_tiefpass" | "звук_фнч" =>
11542 {
11543 let cutoff = self.arg_num(&args, 0, 1.0)? as f32;
11544 if let Some(a) = &self.audio {
11545 a.fx_lowpass(cutoff);
11546 }
11547 return Ok(Value::Unit);
11548 },
11549
11550 #[cfg(not(target_arch = "wasm32"))]
11557 "soft_ball" | "软球" | "ソフトボール" | "소프트볼" | "ลูกบอลนุ่ม" | "توپ_نرم" | "كرة_ناعمة" | "כדור_רך" | "نرم_گیند" | "balle_molle" | "weicher_ball" | "мягкий_шар" =>
11558 {
11559 let x = self.arg_num(&args, 0, 0.)? as f32;
11560 let y = self.arg_num(&args, 1, 0.)? as f32;
11561 let z = self.arg_num(&args, 2, 0.)? as f32;
11562 let r = self.arg_num(&args, 3, 1.0)? as f32;
11563 let b = ling_physics::soft::SoftBody::sphere(
11564 ling_physics::Vec3::new(x, y, z),
11565 r,
11566 8,
11567 12,
11568 1.0,
11569 );
11570 let id = self.soft_bodies.len();
11571 self.soft_bodies.push(b);
11572 return Ok(Value::Number(id as f64));
11573 },
11574 #[cfg(not(target_arch = "wasm32"))]
11575 "soft_step" | "软体步进" | "ソフト更新" | "소프트스텝" | "ก้าวนุ่ม" | "گام_نرم_جسم" | "خطوة_ناعمة" | "צעד_רך" | "نرم_قدم" | "pas_mou" | "weicher_schritt" | "мягкий_шаг" =>
11576 {
11577 let id = self.arg_num(&args, 0, 0.)? as usize;
11578 let dt = self.arg_num(&args, 1, 0.016)? as f32;
11579 let gy = self.arg_num(&args, 2, 15.0)? as f32;
11580 if let Some(b) = self.soft_bodies.get_mut(id) {
11581 b.integrate(dt, ling_physics::Vec3::new(0.0, gy, 0.0), 4);
11582 }
11583 return Ok(Value::Unit);
11584 },
11585 #[cfg(not(target_arch = "wasm32"))]
11586 "soft_bounce" | "软体落地" | "ソフト着地" | "소프트바운스" | "เด้งนุ่ม" | "جهش_نرم" | "ارتداد_ناعم" | "קפיצה_רכה" | "نرم_اچھال" | "rebond_mou" | "weicher_abprall" | "мягкий_отскок" =>
11587 {
11588 let id = self.arg_num(&args, 0, 0.)? as usize;
11589 let fy = self.arg_num(&args, 1, 0.)? as f32;
11590 let rest = self.arg_num(&args, 2, 0.5)? as f32;
11591 if let Some(b) = self.soft_bodies.get_mut(id) {
11592 b.floor_collision(fy, rest);
11593 }
11594 return Ok(Value::Unit);
11595 },
11596 #[cfg(not(target_arch = "wasm32"))]
11597 "soft_contain" | "软体边界" | "ソフト箱" | "소프트경계" | "กล่องนุ่ม" | "محفظه_نرم" | "احتواء_ناعم" | "הכלה_רכה" | "نرم_احاطہ" | "contenir_mou" | "weiche_eindämmung" | "мягкое_сдерживание" =>
11598 {
11599 let id = self.arg_num(&args, 0, 0.)? as usize;
11600 let nx = self.arg_num(&args, 1, -5.)? as f32;
11601 let ny = self.arg_num(&args, 2, -5.)? as f32;
11602 let nz = self.arg_num(&args, 3, -5.)? as f32;
11603 let mx = self.arg_num(&args, 4, 5.)? as f32;
11604 let my = self.arg_num(&args, 5, 5.)? as f32;
11605 let mz = self.arg_num(&args, 6, 5.)? as f32;
11606 let rest = self.arg_num(&args, 7, 0.6)? as f32;
11607 if let Some(b) = self.soft_bodies.get_mut(id) {
11608 b.contain(
11609 ling_physics::Vec3::new(nx, ny, nz),
11610 ling_physics::Vec3::new(mx, my, mz),
11611 rest,
11612 );
11613 }
11614 return Ok(Value::Unit);
11615 },
11616 #[cfg(not(target_arch = "wasm32"))]
11617 "soft_kick" | "软体踢" | "ソフト衝撃" | "소프트킥" | "เตะนุ่ม" | "ضربه_نرم" | "ركلة_ناعمة" | "בעיטה_רכה" | "نرم_ٹھوکر" | "coup_mou" | "weicher_stoß" | "мягкий_удар" =>
11618 {
11619 let id = self.arg_num(&args, 0, 0.)? as usize;
11620 let dx = self.arg_num(&args, 1, 0.)? as f32;
11621 let dy = self.arg_num(&args, 2, 0.)? as f32;
11622 let dz = self.arg_num(&args, 3, 0.)? as f32;
11623 let s = self.arg_num(&args, 4, 0.1)? as f32;
11624 if let Some(b) = self.soft_bodies.get_mut(id) {
11625 b.kick(ling_physics::Vec3::new(dx, dy, dz), s);
11626 }
11627 return Ok(Value::Unit);
11628 },
11629 #[cfg(not(target_arch = "wasm32"))]
11632 "soft_spin" | "软体自旋" | "ソフト回転" | "소프트회전" | "หมุนนุ่ม" | "چرخش_نرم" | "دوران_ناعم" | "סיבוב_רך" | "نرم_گھماؤ" | "rotation_molle" | "weicher_spin" | "мягкое_вращение" =>
11633 {
11634 let id = self.arg_num(&args, 0, 0.)? as usize;
11635 let ax = self.arg_num(&args, 1, 0.)? as f32;
11636 let ay = self.arg_num(&args, 2, 0.)? as f32;
11637 let az = self.arg_num(&args, 3, 0.)? as f32;
11638 let rate = self.arg_num(&args, 4, 0.1)? as f32;
11639 if let Some(b) = self.soft_bodies.get_mut(id) {
11640 b.spin(ling_physics::Vec3::new(ax, ay, az), rate);
11641 }
11642 return Ok(Value::Unit);
11643 },
11644 #[cfg(not(target_arch = "wasm32"))]
11645 "soft_deform" | "形变量" | "変形量" | "변형량" | "ความบิดเบี้ยว" | "تغییرشکل_نرم" | "تشوه_ناعم" | "עיוות_רך" | "نرم_بگاڑ" | "déformer_mou" | "weiches_verformen" | "мягкая_деформация" =>
11646 {
11647 let id = self.arg_num(&args, 0, 0.)? as usize;
11648 let d = self
11649 .soft_bodies
11650 .get(id)
11651 .map(|b| b.deformation())
11652 .unwrap_or(0.0);
11653 return Ok(Value::Number(d as f64));
11654 },
11655 #[cfg(not(target_arch = "wasm32"))]
11658 "soft_angular_speed"
11659 | "软体角速"
11660 | "ソフト角速度"
11661 | "소프트각속도"
11662 | "ความเร็วเชิงมุมนุ่ม" | "سرعت_زاویهای_نرم" | "سرعة_زاوية_ناعمة" | "מהירות_זוויתית_רכה" | "نرم_زاویائی_رفتار" | "vitesse_angulaire_molle" | "weiche_winkelgeschwindigkeit" | "мягкая_угловая_скорость" => {
11663 let id = self.arg_num(&args, 0, 0.)? as usize;
11664 let w = self
11665 .soft_bodies
11666 .get(id)
11667 .map(|b| b.angular_speed())
11668 .unwrap_or(0.0);
11669 return Ok(Value::Number(w as f64));
11670 },
11671 #[cfg(not(target_arch = "wasm32"))]
11672 "soft_centroid" | "软体质心" | "ソフト重心" | "소프트중심" | "จุดศูนย์กลางนุ่ม" | "مرکز_جرم_نرم" | "مركز_ثقل_ناعم" | "מרכז_כובד_רך" | "نرم_مرکز_ثقل" | "centroïde_mou" | "weicher_schwerpunkt" | "мягкий_центроид" =>
11673 {
11674 let id = self.arg_num(&args, 0, 0.)? as usize;
11675 let c = self
11676 .soft_bodies
11677 .get(id)
11678 .map(|b| b.centroid())
11679 .unwrap_or(ling_physics::Vec3::ZERO);
11680 return Ok(Value::List(Rc::new(vec![
11681 Value::Number(c.x as f64),
11682 Value::Number(c.y as f64),
11683 Value::Number(c.z as f64),
11684 ])));
11685 },
11686 #[cfg(not(target_arch = "wasm32"))]
11688 "soft_nodes" | "软体节点" | "ソフト節点" | "소프트노드" | "จุดนุ่ม" | "گرههای_نرم" | "عقد_ناعمة" | "צמתי_רך" | "نرم_نوڈز" | "nœuds_mous" | "weiche_knoten" | "мягкие_узлы" =>
11689 {
11690 let id = self.arg_num(&args, 0, 0.)? as usize;
11691 let mut out = Vec::new();
11692 if let Some(b) = self.soft_bodies.get(id) {
11693 for n in &b.nodes {
11694 out.push(Value::Number(n.pos.x as f64));
11695 out.push(Value::Number(n.pos.y as f64));
11696 out.push(Value::Number(n.pos.z as f64));
11697 }
11698 }
11699 return Ok(Value::List(Rc::new(out)));
11700 },
11701
11702 #[cfg(not(target_arch = "wasm32"))]
11704 "rb_add" | "刚体添加" | "剛体追加" | "강체추가" | "เพิ่มวัตถุแข็ง" | "افزودن_جسم_صلب" | "أضف_جسما_صلبا" | "הוסף_גוף_קשיח" | "سخت_جسم_شامل_کرو" | "ajouter_corps_rigide" | "starrkörper_hinzufügen" | "добавить_твёрдое_тело" =>
11705 {
11706 let x = self.arg_num(&args, 0, 0.)? as f32;
11707 let y = self.arg_num(&args, 1, 0.)? as f32;
11708 let z = self.arg_num(&args, 2, 0.)? as f32;
11709 let mass = self.arg_num(&args, 3, 1.0)? as f32;
11710 let mut b =
11711 ling_physics::rigid::RigidBody::new(ling_physics::Vec3::new(x, y, z), mass);
11712 b.restitution = 0.6;
11713 return Ok(Value::Number(self.rigid_world.add(b) as f64));
11714 },
11715 #[cfg(not(target_arch = "wasm32"))]
11716 "rb_torque" | "扭矩" | "トルク" | "토크" | "แรงบิด" | "گشتاور" | "عزم_دوران" | "מומנט" | "ٹارک" | "couple_corps_rigide" | "starrkörper_drehmoment" | "крутящий_момент_твёрдого_тела" => {
11717 let i = self.arg_num(&args, 0, 0.)? as usize;
11718 let tx = self.arg_num(&args, 1, 0.)? as f32;
11719 let ty = self.arg_num(&args, 2, 0.)? as f32;
11720 let tz = self.arg_num(&args, 3, 0.)? as f32;
11721 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11722 b.apply_torque(ling_physics::Vec3::new(tx, ty, tz));
11723 }
11724 return Ok(Value::Unit);
11725 },
11726 #[cfg(not(target_arch = "wasm32"))]
11727 "rb_spin" | "自旋" | "スピン" | "스핀" | "หมุน" | "چرخش_جسم_صلب" | "دوران_جسم_صلب" | "סיבוב_גוף_קשיח" | "سخت_جسم_گھماؤ" | "spin_corps_rigide" | "starrkörper_spin" | "вращение_твёрдого_тела" => {
11728 let i = self.arg_num(&args, 0, 0.)? as usize;
11729 let wx = self.arg_num(&args, 1, 0.)? as f32;
11730 let wy = self.arg_num(&args, 2, 0.)? as f32;
11731 let wz = self.arg_num(&args, 3, 0.)? as f32;
11732 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11733 b.apply_spin(ling_physics::Vec3::new(wx, wy, wz));
11734 }
11735 return Ok(Value::Unit);
11736 },
11737 #[cfg(not(target_arch = "wasm32"))]
11738 "rb_impulse" | "刚体冲量" | "剛体インパルス" | "강체충격" | "แรงดลแข็ง" | "ضربه_جسم_صلب" | "دفعة_جسم_صلب" | "דחף_גוף_קשיח" | "سخت_جسم_دھکا" | "impulsion_corps_rigide" | "starrkörper_impuls" | "импульс_твёрдого_тела" =>
11739 {
11740 let i = self.arg_num(&args, 0, 0.)? as usize;
11741 let ix = self.arg_num(&args, 1, 0.)? as f32;
11742 let iy = self.arg_num(&args, 2, 0.)? as f32;
11743 let iz = self.arg_num(&args, 3, 0.)? as f32;
11744 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11745 b.apply_impulse(ling_physics::Vec3::new(ix, iy, iz));
11746 }
11747 return Ok(Value::Unit);
11748 },
11749 #[cfg(not(target_arch = "wasm32"))]
11750 "rb_floor" | "刚体落地" | "剛体着地" | "강체바닥" | "พื้นแข็ง" | "کف_جسم_صلب" | "أرضية_جسم_صلب" | "רצפת_גוף_קשיח" | "سخت_جسم_فرش" | "sol_corps_rigide" | "starrkörper_boden" | "пол_твёрдого_тела" =>
11751 {
11752 let i = self.arg_num(&args, 0, 0.)? as usize;
11753 let fy = self.arg_num(&args, 1, 0.)? as f32;
11754 let rest = self.arg_num(&args, 2, 0.6)? as f32;
11755 let fric = self.arg_num(&args, 3, 0.6)? as f32;
11756 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11757 b.bounce_floor(fy, rest, fric);
11758 }
11759 return Ok(Value::Unit);
11760 },
11761 #[cfg(not(target_arch = "wasm32"))]
11762 "rb_gravity" | "刚体重力" | "剛体重力" | "강체중력" | "แรงโน้มถ่วงแข็ง" | "گرانش_جسم_صلب" | "جاذبية_جسم_صلب" | "כבידת_גוף_קשיח" | "سخت_جسم_کشش_ثقل" | "gravité_corps_rigide" | "starrkörper_schwerkraft" | "гравитация_твёрдого_тела" =>
11763 {
11764 let gx = self.arg_num(&args, 0, 0.)? as f32;
11765 let gy = self.arg_num(&args, 1, 9.81)? as f32;
11766 let gz = self.arg_num(&args, 2, 0.)? as f32;
11767 self.rigid_world.gravity = ling_physics::Vec3::new(gx, gy, gz);
11768 return Ok(Value::Unit);
11769 },
11770 #[cfg(not(target_arch = "wasm32"))]
11771 "rb_step" | "刚体步进" | "剛体更新" | "강체스텝" | "ก้าวแข็ง" | "گام_جسم_صلب" | "خطوة_جسم_صلب" | "צעד_גוף_קשיח" | "سخت_جسم_قدم" | "pas_corps_rigide" | "starrkörper_schritt" | "шаг_твёрдого_тела" =>
11772 {
11773 let dt = self.arg_num(&args, 0, 0.016)? as f32;
11774 self.rigid_world.step(dt);
11775 return Ok(Value::Unit);
11776 },
11777 #[cfg(not(target_arch = "wasm32"))]
11778 "rb_pos" | "刚体位置" | "剛体位置" | "강체위치" | "ตำแหน่งแข็ง" | "موقعیت_جسم_صلب" | "موضع_جسم_صلب" | "מיקום_גוף_קשיח" | "سخت_جسم_مقام" | "position_corps_rigide" | "starrkörper_position" | "позиция_твёрдого_тела" =>
11779 {
11780 let i = self.arg_num(&args, 0, 0.)? as usize;
11781 let p = self
11782 .rigid_world
11783 .bodies
11784 .get(i)
11785 .map(|b| b.pos)
11786 .unwrap_or(ling_physics::Vec3::ZERO);
11787 return Ok(Value::List(Rc::new(vec![
11788 Value::Number(p.x as f64),
11789 Value::Number(p.y as f64),
11790 Value::Number(p.z as f64),
11791 ])));
11792 },
11793 #[cfg(not(target_arch = "wasm32"))]
11794 "rb_rot" | "刚体旋转" | "剛体回転" | "강체회전" | "การหมุนแข็ง" | "چرخش_وضعية_جسم_صلب" | "دوران_وضعية_جسم_صلب" | "סיבוב_זווית_גוף_קשיח" | "سخت_جسم_گردش" | "rotation_corps_rigide" | "starrkörper_rotation" | "поворот_твёрдого_тела" =>
11795 {
11796 let i = self.arg_num(&args, 0, 0.)? as usize;
11797 let q = self
11798 .rigid_world
11799 .bodies
11800 .get(i)
11801 .map(|b| b.orientation)
11802 .unwrap_or(ling_physics::Quat::IDENTITY);
11803 return Ok(Value::List(Rc::new(vec![
11804 Value::Number(q.x as f64),
11805 Value::Number(q.y as f64),
11806 Value::Number(q.z as f64),
11807 Value::Number(q.w as f64),
11808 ])));
11809 },
11810
11811 #[cfg(not(target_arch = "wasm32"))]
11813 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" | "بارگذاری_مش" | "حمّل_شبكة" | "טען_מש" | "میش_لوڈ" =>
11814 {
11815 let path = self.arg_str(&args, 0, "");
11816 let resolved = if std::path::Path::new(&path).exists() {
11817 path.clone()
11818 } else if let Some(d) = &self.source_dir {
11819 d.join(&path).to_string_lossy().into_owned()
11820 } else {
11821 path.clone()
11822 };
11823 let bytes = match std::fs::read(&resolved) {
11824 Ok(b) => b,
11825 Err(e) => {
11826 eprintln!("mesh_load failed ({path}): {e}");
11827 return Ok(Value::Number(-1.0));
11828 },
11829 };
11830 if bytes.len() < 16 || &bytes[0..4] != b"LMSH" {
11831 eprintln!("mesh_load: bad header ({path})");
11832 return Ok(Value::Number(-1.0));
11833 }
11834 let rd4 =
11835 |o: usize| -> [u8; 4] { [bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]] };
11836 let height = f32::from_le_bytes(rd4(8));
11837 let ntri = u32::from_le_bytes(rd4(12)) as usize;
11838 let need = 16usize.saturating_add(ntri.saturating_mul(9 * 4 + 3));
11839 if bytes.len() < need {
11840 eprintln!("mesh_load: truncated ({path})");
11841 return Ok(Value::Number(-1.0));
11842 }
11843 let mut pos = Vec::with_capacity(ntri * 3);
11844 let mut col = Vec::with_capacity(ntri);
11845 let mut off = 16usize;
11846 for _ in 0..ntri {
11847 for _k in 0..3 {
11848 let x = f32::from_le_bytes(rd4(off));
11849 let y = f32::from_le_bytes(rd4(off + 4));
11850 let z = f32::from_le_bytes(rd4(off + 8));
11851 off += 12;
11852 pos.push([x, y, z]);
11853 }
11854 col.push([bytes[off], bytes[off + 1], bytes[off + 2]]);
11855 off += 3;
11856 }
11857 eprintln!("mesh_load: {} ({} tris, h={:.2})", path, ntri, height);
11858 let id = self.meshes.len();
11859 self.meshes
11860 .push(crate::gfx::shapes::ColorMesh { pos, col, height });
11861 return Ok(Value::Number(id as f64));
11862 },
11863 #[cfg(target_arch = "wasm32")]
11864 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" | "بارگذاری_مش" | "حمّل_شبكة" | "טען_רשת" | "میش_لوڈ" =>
11865 {
11866 return Ok(Value::Number(-1.0));
11869 },
11870 #[cfg(not(target_arch = "wasm32"))]
11871 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" | "رسم_مش_رنگی" | "ارسم_شبكة_ملونة" | "צייר_רשת_צבעונית" | "رنگین_میش_کھینچو" =>
11872 {
11873 let id = self.arg_num(&args, 0, 0.)? as usize;
11875 let cx = self.arg_num(&args, 1, 0.)? as f32;
11876 let cy = self.arg_num(&args, 2, 0.)? as f32;
11877 let cz = self.arg_num(&args, 3, 0.)? as f32;
11878 let sc = self.arg_num(&args, 4, 1.)? as f32;
11879 let yaw = self.arg_num(&args, 5, 0.)? as f32;
11880 let sway = self.arg_num(&args, 6, 0.)? as f32;
11881 let arm = self.arg_num(&args, 7, 0.)? as f32;
11882 let lean = self.arg_num(&args, 8, 0.)? as f32;
11883 let leg = self.arg_num(&args, 9, 0.)? as f32;
11884 let tuck = self.arg_num(&args, 10, 0.)? as f32;
11885 if id < self.meshes.len() {
11886 let m = &self.meshes[id];
11887 let mut gfx = self.gfx.borrow_mut();
11888 gfx.draw_color_mesh(m, cx, cy, cz, sc, yaw, sway, arm, lean, leg, tuck);
11889 }
11890 return Ok(Value::Unit);
11891 },
11892 #[cfg(target_arch = "wasm32")]
11893 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" | "رسم_مش_رنگی" | "ارسم_شبكة_ملونة" | "צייר_רשת_צבעונית" | "رنگین_میش_کھینچو" =>
11894 {
11895 return Ok(Value::Unit);
11896 },
11897
11898 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" | "مایع_جدید" | "سائل_جديد" | "נוזל_חדש" | "نیا_مائع" | "nouveau_liquide" | "neue_flüssigkeit" | "новая_жидкость" =>
11900 {
11901 let w = self.arg_num(&args, 0, 64.)? as usize;
11902 let h = self.arg_num(&args, 1, 64.)? as usize;
11903 let id = self.liquids.len();
11904 self.liquids
11905 .push(ling_physics::liquid::LiquidGrid::new(w, h));
11906 return Ok(Value::Number(id as f64));
11907 },
11908 "liquid_set_colors" | "液体颜色" | "液体配色" | "액체색상" | "สีของเหลว" | "تنظیم_رنگ_مایع" | "عيّن_ألوان_السائل" | "קבע_צבעי_נוזל" | "مائع_رنگ_مقرر_کرو" =>
11909 {
11910 let id = self.arg_num(&args, 0, 0.)? as usize;
11911 let wr = self.arg_num(&args, 1, 40.)? as f32;
11912 let wg = self.arg_num(&args, 2, 110.)? as f32;
11913 let wb = self.arg_num(&args, 3, 235.)? as f32;
11914 let or_ = self.arg_num(&args, 4, 240.)? as f32;
11915 let og = self.arg_num(&args, 5, 175.)? as f32;
11916 let ob = self.arg_num(&args, 6, 45.)? as f32;
11917 if let Some(g) = self.liquids.get_mut(id) {
11918 g.set_colors(wr, wg, wb, or_, og, ob);
11919 }
11920 return Ok(Value::Unit);
11921 },
11922 "liquid_splat" | "液体注入" | "液体追加" | "액체분사" | "หยดของเหลว" | "پاشش_مایع" | "بقعة_سائل" | "התזת_נוזל" | "مائع_چھینٹا" | "éclaboussure_liquide" | "flüssigkeit_spritzer" | "брызги_жидкости" =>
11923 {
11924 let id = self.arg_num(&args, 0, 0.)? as usize;
11925 let x = self.arg_num(&args, 1, 0.)? as f32;
11926 let y = self.arg_num(&args, 2, 0.)? as f32;
11927 let kind = self.arg_num(&args, 3, 0.)? as i32;
11928 let amt = self.arg_num(&args, 4, 1.0)? as f32;
11929 let rad = self.arg_num(&args, 5, 4.0)? as f32;
11930 if let Some(g) = self.liquids.get_mut(id) {
11931 g.splat(x, y, kind, amt, rad);
11932 }
11933 return Ok(Value::Unit);
11934 },
11935 "liquid_gravity" | "液体重力" | "液体重力ベクトル" | "액체중력" | "แรงโน้มถ่วงเหลว" | "گرانش_مایع" | "جاذبية_السائل" | "כבידת_נוזל" | "مائع_کشش_ثقل" | "gravité_liquide" | "flüssigkeit_schwerkraft" | "гравитация_жидкости" =>
11936 {
11937 let id = self.arg_num(&args, 0, 0.)? as usize;
11938 let gx = self.arg_num(&args, 1, 0.)? as f32;
11939 let gy = self.arg_num(&args, 2, 60.)? as f32;
11940 if let Some(g) = self.liquids.get_mut(id) {
11941 g.set_gravity(gx, gy);
11942 }
11943 return Ok(Value::Unit);
11944 },
11945 "liquid_step" | "液体步进" | "液体更新" | "액체스텝" | "ก้าวของเหลว" | "گام_مایع" | "خطوة_السائل" | "צעד_נוזל" | "مائع_قدم" | "pas_liquide" | "flüssigkeit_schritt" | "шаг_жидкости" =>
11946 {
11947 let id = self.arg_num(&args, 0, 0.)? as usize;
11948 let dt = self.arg_num(&args, 1, 0.016)? as f32;
11949 if let Some(g) = self.liquids.get_mut(id) {
11950 g.step(dt);
11951 }
11952 return Ok(Value::Unit);
11953 },
11954 "liquid_step_all"
11960 | "液体全步进"
11961 | "液体全更新"
11962 | "전체액체스텝"
11963 | "ก้าวของเหลวทั้งหมด" | "گام_همه_مایعات" | "خطوة_كل_السوائل" | "צעד_כל_הנוזלים" | "تمام_مائع_قدم" => {
11964 let dt = self.arg_num(&args, 0, 0.016)? as f32;
11965 ling_physics::liquid::step_all(&mut self.liquids, dt);
11966 return Ok(Value::Unit);
11967 },
11968 "liquid_rainbow" | "液体彩虹" | "液体虹" | "액체무지개" | "ของเหลวสายรุ้ง" | "مایع_رنگینکمان" | "سائل_قوس_قزح" | "נוזל_קשת" | "قوس_قزح_مائع" | "arc_en_ciel_liquide" | "flüssigkeit_regenbogen" | "радуга_жидкости" =>
11970 {
11971 let id = self.arg_num(&args, 0, 0.)? as usize;
11972 let on = self.arg_num(&args, 1, 1.0)? > 0.5;
11973 if let Some(g) = self.liquids.get_mut(id) {
11974 g.rainbow = on;
11975 }
11976 return Ok(Value::Unit);
11977 },
11978 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" | "ترکیب_مایع" | "مزج_سائل" | "ערבוב_נוזל" | "مائع_ملاؤ" | "mélanger_liquide" | "flüssigkeit_mischen" | "смешать_жидкость" =>
11980 {
11981 let id = self.arg_num(&args, 0, 0.)? as usize;
11982 let m = self.liquids.get(id).map(|g| g.mix_amount()).unwrap_or(0.0);
11983 return Ok(Value::Number(m as f64));
11984 },
11985 #[cfg(not(target_arch = "wasm32"))]
11987 "liquid_draw" | "绘制液体" | "液体描画" | "액체그리기" | "วาดของเหลว" | "رسم_مایع" | "ارسم_سائلا" | "צייר_נוזל" | "مائع_کھینچو" | "dessiner_liquide" | "flüssigkeit_zeichnen" | "рисовать_жидкость" =>
11988 {
11989 let id = self.arg_num(&args, 0, 0.)? as usize;
11990 let sx = self.arg_num(&args, 1, 0.)? as i32;
11991 let sy = self.arg_num(&args, 2, 0.)? as i32;
11992 let scale = (self.arg_num(&args, 3, 4.)? as i32).max(1);
11993 if id < self.liquids.len() {
11994 let (gw, gh) = {
11995 let g = &self.liquids[id];
11996 (g.w, g.h)
11997 };
11998 let mut gfx = self.gfx.borrow_mut();
11999 let (w, h) = (gfx.width as i32, gfx.height as i32);
12000 let g = &self.liquids[id];
12001 for cy in 0..gh {
12002 for cx in 0..gw {
12003 let col = g.sample_rgb(cx, cy);
12004 let bx = sx + cx as i32 * scale;
12005 let by = sy + cy as i32 * scale;
12006 for dy in 0..scale {
12007 for dx in 0..scale {
12008 let px = bx + dx;
12009 let py = by + dy;
12010 if px >= 0 && py >= 0 && px < w && py < h {
12011 gfx.buffer[(py * w + px) as usize] = col;
12012 }
12013 }
12014 }
12015 }
12016 }
12017 }
12018 return Ok(Value::Unit);
12019 },
12020 "liquid_draw_surface" | "液体贴面" | "液体曲面" | "액체곡면" | "ของเหลวบนพื้นผิว" | "رسم_سطح_مایع" | "ارسم_سطح_السائل" | "צייר_משטח_נוזל" | "مائع_سطح_کھینچو" | "dessiner_surface_liquide" | "flüssigkeit_oberfläche_zeichnen" | "рисовать_поверхность_жидкости" =>
12023 {
12024 #[cfg(not(target_arch = "wasm32"))]
12025 {
12026 let id = self.arg_num(&args, 0, 0.)? as usize;
12027 let kind = self.arg_num(&args, 1, 1.)? as i32;
12028 let cx = self.arg_num(&args, 2, 0.)? as f32;
12029 let cy = self.arg_num(&args, 3, 0.)? as f32;
12030 let cz = self.arg_num(&args, 4, 0.)? as f32;
12031 let radius = self.arg_num(&args, 5, 2.0)? as f32;
12032 let height = self.arg_num(&args, 6, 3.0)? as f32;
12033 if id < self.liquids.len() {
12034 let (gw, gh) = {
12035 let g = &self.liquids[id];
12036 (g.w, g.h)
12037 };
12038 let mut gfx = self.gfx.borrow_mut();
12039 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
12040 let cam = gfx.camera.clone();
12041 let near = -cam.zdist + 0.05;
12042 let g = &self.liquids[id];
12043 let tau = std::f32::consts::TAU;
12044 let pi = std::f32::consts::PI;
12045 let sp = |u: f32, v: f32| -> [f32; 3] {
12047 if kind == 0 {
12048 [
12049 cx + (u - 0.5) * 2.0 * radius,
12050 cy,
12051 cz + (v - 0.5) * 2.0 * radius,
12052 ]
12053 } else if kind == 2 {
12054 let th = u * tau;
12055 [
12056 cx + th.cos() * radius,
12057 cy + (v - 0.5) * height,
12058 cz + th.sin() * radius,
12059 ]
12060 } else if kind == 3 {
12061 let th = u * tau;
12062 let rr = radius * (1.0 - v);
12063 [
12064 cx + th.cos() * rr,
12065 cy + (v - 0.5) * height,
12066 cz + th.sin() * rr,
12067 ]
12068 } else if kind == 4 {
12069 let th = u * tau;
12070 let ph = v * pi * 0.5;
12071 [
12072 cx + ph.sin() * th.cos() * radius,
12073 cy - ph.cos() * radius,
12074 cz + ph.sin() * th.sin() * radius,
12075 ]
12076 } else {
12077 let th = u * tau;
12078 let ph = v * pi;
12079 [
12080 cx + ph.sin() * th.cos() * radius,
12081 cy + ph.cos() * radius,
12082 cz + ph.sin() * th.sin() * radius,
12083 ]
12084 }
12085 };
12086 let nrm = |u: f32, v: f32| -> [f32; 3] {
12087 if kind == 0 {
12088 [0.0, -1.0, 0.0]
12089 } else if kind == 2 {
12090 let th = u * tau;
12091 [th.cos(), 0.0, th.sin()]
12092 } else if kind == 3 {
12093 let th = u * tau;
12094 let s = (radius / height.max(0.01)).atan();
12095 [th.cos() * s.cos(), s.sin(), th.sin() * s.cos()]
12096 } else if kind == 4 {
12097 let th = u * tau;
12098 let ph = v * pi * 0.5;
12099 [ph.sin() * th.cos(), -ph.cos(), ph.sin() * th.sin()]
12100 } else {
12101 let th = u * tau;
12102 let ph = v * pi;
12103 [ph.sin() * th.cos(), ph.cos(), ph.sin() * th.sin()]
12104 }
12105 };
12106 let gwf = gw as f32;
12107 let ghf = gh as f32;
12108 let mut cyc = 0usize;
12109 while cyc < gh {
12110 let mut cxc = 0usize;
12111 while cxc < gw {
12112 let uc = (cxc as f32 + 0.5) / gwf;
12114 let vc = (cyc as f32 + 0.5) / ghf;
12115 let c = sp(uc, vc);
12116 let n = nrm(uc, vc);
12117 let dc = cam.depth(c[0], c[1], c[2]);
12118 if dc > near {
12119 let cull = kind != 0
12120 && cam.depth(
12121 c[0] + n[0] * 0.06,
12122 c[1] + n[1] * 0.06,
12123 c[2] + n[2] * 0.06,
12124 ) > dc;
12125 if !cull {
12126 let u0 = cxc as f32 / gwf;
12128 let u1 = (cxc + 1) as f32 / gwf;
12129 let v0 = cyc as f32 / ghf;
12130 let v1 = (cyc + 1) as f32 / ghf;
12131 let q = [sp(u0, v0), sp(u1, v0), sp(u1, v1), sp(u0, v1)];
12132 let mut poly: Vec<[f32; 2]> = Vec::with_capacity(5);
12133 let mut ok = true;
12134 for p in &q {
12135 if cam.depth(p[0], p[1], p[2]) <= near {
12136 ok = false;
12137 break;
12138 }
12139 let (sx, sy, _) = cam.project(p[0], p[1], p[2]);
12140 poly.push([sx, sy]);
12141 }
12142 if ok {
12143 let p0 = poly[0];
12144 poly.push(p0);
12145 let col = g.sample_rgb(cxc, cyc);
12146 crate::gfx::raster::fill_contours_aa(
12147 &mut gfx.buffer,
12148 w,
12149 h,
12150 col,
12151 add,
12152 std::slice::from_ref(&poly),
12153 );
12154 }
12155 }
12156 }
12157 cxc += 1;
12158 }
12159 cyc += 1;
12160 }
12161 }
12162 }
12163 #[cfg(target_arch = "wasm32")]
12164 {
12165 }
12168 return Ok(Value::Unit);
12169 },
12170 #[cfg(not(target_arch = "wasm32"))]
12173 "sparkle" | "闪光" | "きらめき" | "반짝임" | "ประกาย" | "درخشش" | "بريق" | "נצנוץ" | "چمک" | "scintillement" | "funkeln" | "искриться" => {
12174 let x = self.arg_num(&args, 0, 0.)? as f32;
12175 let y = self.arg_num(&args, 1, 0.)? as f32;
12176 let ww = self.arg_num(&args, 2, 200.)? as f32;
12177 let hh = self.arg_num(&args, 3, 200.)? as f32;
12178 let count = self.arg_num(&args, 4, 40.)? as i32;
12179 let t = self.arg_num(&args, 5, 0.)? as f32;
12180 let mut gfx = self.gfx.borrow_mut();
12181 let (w, h, add, color) = (gfx.width, gfx.height, gfx.blend == 1, gfx.color);
12182 let (cr, cg, cb) = (
12183 (color >> 16 & 0xFF) as f32,
12184 (color >> 8 & 0xFF) as f32,
12185 (color & 0xFF) as f32,
12186 );
12187 let mut n = 0i32;
12188 while n < count {
12189 let hsh = (n as u32).wrapping_mul(2654435761).wrapping_add(0x9E3779B9);
12190 let u = ((hsh >> 8) & 1023) as f32 / 1023.0;
12191 let v = ((hsh >> 18) & 1023) as f32 / 1023.0;
12192 let phase = (hsh & 255) as f32 / 255.0;
12193 let tw = (t * 3.0 + phase * std::f32::consts::TAU + n as f32).sin() * 0.5 + 0.5;
12194 let sz = 1.5 + tw * 5.0;
12195 let px = x + u * ww;
12196 let py = y + v * hh;
12197 let b = tw * tw; let col =
12199 (((cr * b) as u32) << 16) | (((cg * b) as u32) << 8) | ((cb * b) as u32);
12200 crate::gfx::raster::draw_line_aa(
12201 &mut gfx.buffer,
12202 w,
12203 h,
12204 col,
12205 add,
12206 px - sz,
12207 py,
12208 px + sz,
12209 py,
12210 );
12211 crate::gfx::raster::draw_line_aa(
12212 &mut gfx.buffer,
12213 w,
12214 h,
12215 col,
12216 add,
12217 px,
12218 py - sz,
12219 px,
12220 py + sz,
12221 );
12222 let d = sz * 0.55;
12223 crate::gfx::raster::draw_line_aa(
12224 &mut gfx.buffer,
12225 w,
12226 h,
12227 col,
12228 add,
12229 px - d,
12230 py - d,
12231 px + d,
12232 py + d,
12233 );
12234 crate::gfx::raster::draw_line_aa(
12235 &mut gfx.buffer,
12236 w,
12237 h,
12238 col,
12239 add,
12240 px - d,
12241 py + d,
12242 px + d,
12243 py - d,
12244 );
12245 n += 1;
12246 }
12247 return Ok(Value::Unit);
12248 },
12249
12250 #[cfg(not(target_arch = "wasm32"))]
12256 "dialog_show" | "对话显示" | "会話表示" | "대화표시" | "แสดงบทสนทนา" | "نمایش_گفتگو" | "اعرض_الحوار" | "הצג_דיאלוג" | "مکالمہ_دکھاؤ" | "afficher_dialogue" | "dialog_anzeigen" | "показать_диалог" =>
12257 {
12258 let text = self.arg_str(&args, 0, "");
12259 let cps = self.arg_num(&args, 1, 32.0)? as f32;
12260 self.dialog = Some(ling_game::dialog::Dialog::new(&text, cps));
12261 return Ok(Value::Unit);
12262 },
12263 #[cfg(not(target_arch = "wasm32"))]
12264 "dialog_step" | "对话步进" | "会話更新" | "대화스텝" | "ก้าวบทสนทนา" | "گام_گفتگو" | "خطوة_الحوار" | "צעד_דיאלוג" | "مکالمہ_قدم" | "pas_dialogue" | "dialog_schritt" | "шаг_диалога" =>
12265 {
12266 let dt = self.arg_num(&args, 0, 0.016)? as f32;
12267 if let Some(d) = self.dialog.as_mut() {
12268 d.update(dt);
12269 }
12270 return Ok(Value::Unit);
12271 },
12272 #[cfg(not(target_arch = "wasm32"))]
12273 "dialog_advance" | "对话推进" | "会話送り" | "대화진행" | "เลื่อนบทสนทนา" | "پیشروی_گفتگو" | "تقدّم_الحوار" | "קדם_דיאלוג" | "مکالمہ_آگے_بڑھاؤ" | "avancer_dialogue" | "dialog_weiter" | "продолжить_диалог" =>
12274 {
12275 if let Some(d) = self.dialog.as_mut() {
12276 d.advance();
12277 }
12278 return Ok(Value::Unit);
12279 },
12280 #[cfg(not(target_arch = "wasm32"))]
12281 "dialog_active" | "对话激活" | "会話中" | "대화중" | "บทสนทนาทำงาน" | "گفتگو_فعال" | "الحوار_نشط" | "דיאלוג_פעיל" | "مکالمہ_فعال" | "dialogue_actif" | "dialog_aktiv" | "диалог_активен" =>
12282 {
12283 let a = self
12284 .dialog
12285 .as_ref()
12286 .map(|d| !d.is_closed())
12287 .unwrap_or(false);
12288 return Ok(Value::Bool(a));
12289 },
12290 #[cfg(not(target_arch = "wasm32"))]
12291 "dialog_typing" | "对话打字" | "会話タイプ中" | "대화타이핑" | "กำลังพิมพ์บทสนทนา" | "گفتگو_در_حال_تایپ" | "الحوار_يكتب" | "דיאלוג_מקליד" | "مکالمہ_ٹائپنگ" | "dialogue_frappe" | "dialog_tippen" | "диалог_печатает" =>
12292 {
12293 use ling_game::dialog::Dialog;
12294
12295 let a = self
12296 .dialog
12297 .as_ref()
12298 .map(|d: &Dialog| !d.is_closed() && d.is_typing())
12299 .unwrap_or(false);
12300 return Ok(Value::Bool(a));
12301 },
12302 #[cfg(not(target_arch = "wasm32"))]
12303 "dialog_close" | "对话关闭" | "会話閉じる" | "대화닫기" | "ปิดบทสนทนา" | "بستن_گفتگو" | "أغلق_الحوار" | "סגור_דיאלוג" | "مکالمہ_بند" | "fermer_dialogue" | "dialog_schließen" | "закрыть_диалог" =>
12304 {
12305 self.dialog = None;
12306 return Ok(Value::Unit);
12307 },
12308 #[cfg(not(target_arch = "wasm32"))]
12310 "dialog_color" | "对话颜色" | "会話色" | "대화색" | "สีบทสนทนา" | "رنگ_گفتگو" | "لون_الحوار" | "צבע_דיאלוג" | "مکالمہ_رنگ" | "couleur_dialogue" | "dialog_farbe" | "цвет_диалога" =>
12311 {
12312 let role = (self.arg_num(&args, 0, 0.0)? as usize).min(3);
12313 let r = self.arg_num(&args, 1, 255.0)? as u32 & 0xFF;
12314 let g = self.arg_num(&args, 2, 255.0)? as u32 & 0xFF;
12315 let b = self.arg_num(&args, 3, 255.0)? as u32 & 0xFF;
12316 self.dialog_colors[role] = (r << 16) | (g << 8) | b;
12317 return Ok(Value::Unit);
12318 },
12319 #[cfg(not(target_arch = "wasm32"))]
12321 "dialog_draw" | "对话绘制" | "会話描画" | "대화그리기" | "วาดบทสนทนา" | "رسم_گفتگو" | "ارسم_الحوار" | "צייר_דיאלוג" | "مکالمہ_کھینچو" | "dessiner_dialogue" | "dialog_zeichnen" | "рисовать_диалог" =>
12322 {
12323 let x = self.arg_num(&args, 0, 40.0)? as f32;
12324 let y = self.arg_num(&args, 1, 0.0)? as f32;
12325 let ww = self.arg_num(&args, 2, 720.0)? as f32;
12326 let hh = self.arg_num(&args, 3, 150.0)? as f32;
12327 let font = self.arg_num(&args, 4, -1.0)? as i64;
12328 let t = (crate::runtime::now_secs() - self.start_time_secs) as f32;
12329 self.render_dialog(x, y, ww, hh, font, t);
12330 return Ok(Value::Unit);
12331 },
12332
12333 #[cfg(not(target_arch = "wasm32"))]
12338 "text_poll" => {
12339 const TEXT_BUFFER_MAX: usize = 240;
12340 #[cfg(windows)]
12349 {
12350 let topmost = self.gfx.borrow().topmost_window;
12351 if topmost {
12352 if !window_is_foreground(self.gfx.borrow().hwnd) {
12353 return Ok(Value::Str(self.text_buffer.clone()));
12354 }
12355 let shift = os_key_down(VK_SHIFT);
12356 let now = crate::runtime::now_secs();
12357 let mut gfx = self.gfx.borrow_mut();
12358 let back_idx = VK_BACK as usize;
12359 let back_down = os_key_down(VK_BACK);
12360 let back_was = gfx.raw_keys_prev[back_idx];
12361 let (mut back_since, mut back_fire) = (
12362 gfx.raw_keys_down_since[back_idx],
12363 gfx.raw_keys_last_fire[back_idx],
12364 );
12365 if key_repeat_fire(now, back_down, back_was, &mut back_since, &mut back_fire) {
12366 self.text_buffer.pop();
12367 }
12368 gfx.raw_keys_down_since[back_idx] = back_since;
12369 gfx.raw_keys_last_fire[back_idx] = back_fire;
12370 gfx.raw_keys_prev[back_idx] = back_down;
12371 for &vk in TEXT_POLL_VKS {
12372 let idx = (vk as usize) & 0xFF;
12373 let down = os_key_down(vk);
12374 let was = gfx.raw_keys_prev[idx];
12375 let (mut since, mut fire) =
12376 (gfx.raw_keys_down_since[idx], gfx.raw_keys_last_fire[idx]);
12377 if key_repeat_fire(now, down, was, &mut since, &mut fire) {
12378 if let Some(c) = vk_char(vk, shift) {
12379 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12380 self.text_buffer.push(c);
12381 }
12382 }
12383 }
12384 gfx.raw_keys_down_since[idx] = since;
12385 gfx.raw_keys_last_fire[idx] = fire;
12386 gfx.raw_keys_prev[idx] = down;
12387 }
12388 return Ok(Value::Str(self.text_buffer.clone()));
12389 }
12390 }
12391 let (keys, shift) = {
12392 let gfx = self.gfx.borrow();
12393 match gfx.window.as_ref() {
12394 Some(w) => (
12395 w.get_keys_pressed(minifb::KeyRepeat::Yes),
12396 w.is_key_down(minifb::Key::LeftShift)
12397 || w.is_key_down(minifb::Key::RightShift),
12398 ),
12399 None => (Vec::new(), false),
12400 }
12401 };
12402 for k in keys {
12403 if k == minifb::Key::Backspace {
12404 self.text_buffer.pop();
12405 } else if let Some(c) = key_char(k, shift) {
12406 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12407 self.text_buffer.push(c);
12408 }
12409 }
12410 }
12411 return Ok(Value::Str(self.text_buffer.clone()));
12412 },
12413 #[cfg(target_arch = "wasm32")]
12414 "text_poll" => {
12415 return Ok(Value::Str(self.text_buffer.clone()));
12416 },
12417 "text_get" => return Ok(Value::Str(self.text_buffer.clone())),
12418 "text_set" => {
12419 self.text_buffer = self.arg_str(&args, 0, "");
12420 return Ok(Value::Unit);
12421 },
12422 "text_clear" => {
12423 self.text_buffer.clear();
12424 return Ok(Value::Unit);
12425 },
12426 #[cfg(not(target_arch = "wasm32"))]
12428 "record_frame" => {
12429 let n = self.record_n;
12430 let (buf, w, h) = {
12431 let gfx = self.gfx.borrow();
12432 (gfx.buffer.clone(), gfx.width, gfx.height)
12433 };
12434 let _ = std::fs::create_dir_all("recordings");
12435 let mut out = Vec::with_capacity(w * h * 3 + 32);
12436 out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
12437 for px in &buf {
12438 let p = *px;
12439 out.push((p >> 16) as u8);
12440 out.push((p >> 8) as u8);
12441 out.push(p as u8);
12442 }
12443 let _ = std::fs::write(format!("recordings/frame_{n:05}.ppm"), out);
12444 self.record_n += 1;
12445 return Ok(Value::Number(n as f64));
12446 },
12447 "record_count" => return Ok(Value::Number(self.record_n as f64)),
12448 #[cfg(not(target_arch = "wasm32"))]
12450 "screenshot" | "บันทึกภาพ" | "عکسصفحه" | "لقطة_شاشة" | "צילום_מסך" | "اسکرین_شاٹ" => {
12451 let mode = self.arg_str(&args, 0, "game");
12452 let (buf, w, h) = {
12453 let gfx = self.gfx.borrow();
12454 (gfx.buffer.clone(), gfx.width, gfx.height)
12455 };
12456 let _ = std::fs::create_dir_all("screenshots");
12457 let ts = std::time::SystemTime::now()
12458 .duration_since(std::time::UNIX_EPOCH)
12459 .map(|d| d.as_secs())
12460 .unwrap_or(0);
12461 let safe: String = mode
12462 .chars()
12463 .map(|c| if c.is_alphanumeric() { c } else { '_' })
12464 .collect();
12465 let path = format!("screenshots/ss_{ts}_{safe}_{w}x{h}.png");
12466 let mut rgb = Vec::with_capacity(w * h * 3);
12467 for px in &buf {
12468 let p = *px;
12469 rgb.push((p >> 16) as u8);
12470 rgb.push((p >> 8) as u8);
12471 rgb.push(p as u8);
12472 }
12473 if let Some(img) = image::RgbImage::from_raw(w as u32, h as u32, rgb) {
12474 let _ = img.save(&path);
12475 }
12476 return Ok(Value::Str(path));
12477 },
12478 #[cfg(not(target_arch = "wasm32"))]
12482 "mic_capture" => {
12483 if let Some(mic) = self.mic.as_ref() {
12484 let s = mic.latest_samples();
12485 self.mic_buffer.extend_from_slice(&s);
12486 let cap = 96_000usize; if self.mic_buffer.len() > cap {
12488 let drop = self.mic_buffer.len() - cap;
12489 self.mic_buffer.drain(0..drop);
12490 }
12491 }
12492 return Ok(Value::Number(self.mic_buffer.len() as f64));
12493 },
12494 #[cfg(not(target_arch = "wasm32"))]
12496 "mic_seed" => {
12497 let mut bytes = Vec::with_capacity(self.mic_buffer.len() * 4);
12498 for f in &self.mic_buffer {
12499 bytes.extend_from_slice(&f.to_le_bytes());
12500 }
12501 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(&bytes))));
12502 },
12503 #[cfg(not(target_arch = "wasm32"))]
12504 "mic_clear" => {
12505 self.mic_buffer.clear();
12506 return Ok(Value::Number(0.0));
12507 },
12508 #[cfg(not(target_arch = "wasm32"))]
12511 "flush_3d" | "render_3d" => {
12512 let mut gfx = self.gfx.borrow_mut();
12513 if !gfx.depth_queue.is_empty() {
12514 let w = gfx.width;
12515 let h = gfx.height;
12516 let dt = gfx.depth_test;
12517 let reset_z = gfx.zbuf_needs_clear;
12518 let (bm, ba) = (gfx.blend, gfx.alpha);
12519 let aa = gfx.antialias;
12520 let queue = std::mem::take(&mut gfx.depth_queue);
12521 {
12522 let g = &mut *gfx;
12523 let z = if dt { Some(&mut g.depth_buf) } else { None };
12524 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12525 }
12526 gfx.zbuf_needs_clear = false;
12527 gfx.depth_queue.set_state(bm, ba); }
12529 return Ok(Value::Unit);
12530 },
12531 #[cfg(target_arch = "wasm32")]
12532 "flush_3d" | "render_3d" => {
12533 let mut gfx = self.gfx.borrow_mut();
12534 if !gfx.depth_queue.is_empty() {
12535 let w = gfx.width;
12536 let h = gfx.height;
12537 let dt = gfx.depth_test;
12538 let reset_z = gfx.zbuf_needs_clear;
12539 let (bm, ba) = (gfx.blend, gfx.alpha);
12540 let aa = gfx.antialias;
12541 let queue = std::mem::take(&mut gfx.depth_queue);
12542 {
12543 let g = &mut *gfx;
12544 let z = if dt { Some(&mut g.depth_buf) } else { None };
12545 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12546 }
12547 gfx.zbuf_needs_clear = false;
12548 gfx.depth_queue.set_state(bm, ba);
12549 }
12550 return Ok(Value::Unit);
12551 },
12552
12553 "flush_post" | "post_now" | "포스트플러시" | "后期冲刷" => {
12558 let mut gfx = self.gfx.borrow_mut();
12559 if !gfx.depth_queue.is_empty() {
12560 let w = gfx.width;
12561 let h = gfx.height;
12562 let dt = gfx.depth_test;
12563 let reset_z = gfx.zbuf_needs_clear;
12564 let (bm, ba) = (gfx.blend, gfx.alpha);
12565 let aa = gfx.antialias;
12566 let queue = std::mem::take(&mut gfx.depth_queue);
12567 {
12568 let g = &mut *gfx;
12569 let z = if dt { Some(&mut g.depth_buf) } else { None };
12570 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12571 }
12572 gfx.zbuf_needs_clear = false;
12573 gfx.depth_queue.set_state(bm, ba);
12574 }
12575 gfx.toon_post_process();
12576 gfx.post_done = true;
12577 return Ok(Value::Unit);
12578 },
12579
12580 #[cfg(not(target_arch = "wasm32"))]
12583 "screen_distort" | "บิดจอ" | "屏幕扭曲" | "画面歪み" | "화면왜곡" | "اعوجاج_صفحه" | "شوّه_الشاشة" | "עוות_מסך" | "اسکرین_ڈسٹورٹ" =>
12584 {
12585 let amount = self.arg_num(&args, 0, 8.0)? as f32;
12586 let t = self.arg_num(&args, 1, 0.0)? as f32;
12587 let step = self.arg_num(&args, 2, 1.0)?.max(1.0) as usize;
12590 let _d = std::time::Instant::now();
12591 self.gfx.borrow_mut().distort(amount, t, step);
12592 ling_phase_add(phase::DISTORT, _d.elapsed().as_nanos());
12593 return Ok(Value::Unit);
12594 },
12595
12596 "set_rim" | "设置边缘光" | "リム設定" | "림라이트" | "ตั้งขอบเรือง" | "تنظیم_نور_لبه" | "عيّن_إضاءة_الحافة" | "קבע_תאורת_קצה" | "رم_لائٹ_مقرر_کرو" | "définir_contour_lumineux" | "rimlicht_setzen" | "задать_контурный_свет" =>
12597 {
12598 let s = self.arg_num(&args, 0, 0.6)? as f32;
12599 let r = self.arg_num(&args, 1, 115.)? as f32 / 255.0;
12600 let g = self.arg_num(&args, 2, 217.)? as f32 / 255.0;
12601 let b = self.arg_num(&args, 3, 255.)? as f32 / 255.0;
12602 let mut gfx = self.gfx.borrow_mut();
12603 gfx.shade.rim = s;
12604 gfx.shade.rim_color = [r, g, b];
12605 return Ok(Value::Unit);
12606 },
12607
12608 n if crate::gfx::shapes::canon(n).is_some() => {
12617 let kind = crate::gfx::shapes::canon(n).unwrap();
12618 let cx = self.arg_num(&args, 0, 0.)? as f32;
12619 let cy = self.arg_num(&args, 1, 0.)? as f32;
12620 let cz = self.arg_num(&args, 2, 0.)? as f32;
12621 let sx = self.arg_num(&args, 3, 1.)? as f32;
12622 let sy = self.arg_num(&args, 4, 1.)? as f32;
12623 let sz = self.arg_num(&args, 5, 1.)? as f32;
12624 let rx = self.arg_num(&args, 6, 0.)? as f32;
12625 let ry = self.arg_num(&args, 7, 0.)? as f32;
12626 let rz = self.arg_num(&args, 8, 0.)? as f32;
12627 let mode = self.arg_num(&args, 9, 0.)? as i32;
12628 let e0 = self.arg_num(&args, 10, 0.)? as f32;
12629 let e1 = self.arg_num(&args, 11, 0.)? as f32;
12630 let e2 = self.arg_num(&args, 12, 0.)? as f32;
12631 if let Some(mesh) = crate::gfx::shapes::build(
12632 kind,
12633 [cx, cy, cz, sx, sy, sz, rx, ry, rz],
12634 e0,
12635 e1,
12636 e2,
12637 ) {
12638 let mut gfx = self.gfx.borrow_mut();
12639 gfx.emit_mesh(&mesh, mode);
12640 }
12641 return Ok(Value::Unit);
12642 },
12643
12644 _ => {},
12645 }
12646
12647 if let Some(field_names) = self.structs.get(name).cloned() {
12649 if args.len() != field_names.len() {
12650 return Err(EvalErr::from(format!(
12651 "{name} expects {} field(s), got {}",
12652 field_names.len(),
12653 args.len()
12654 )));
12655 }
12656 let fields = field_names.into_iter().zip(args).collect();
12657 return Ok(Value::Struct { name: name.to_string(), fields });
12658 }
12659
12660 if let Some((enum_name, arity)) = self.enum_variants.get(name).cloned() {
12662 if args.len() != arity {
12663 return Err(EvalErr::from(format!(
12664 "{name} expects {arity} value(s), got {}",
12665 args.len()
12666 )));
12667 }
12668 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
12669 return Ok(Value::Variant { enum_name, variant, payload: args });
12670 }
12671
12672 #[cfg(target_arch = "wasm32")]
12673 if let Some(v) = wasm_unsupported_builtin(name) {
12674 return Ok(v);
12675 }
12676
12677 Err(EvalErr::from(format!("unknown function '{name}'")))
12678 }
12679
12680 fn call_value(&mut self, v: Value, args: Vec<Value>) -> EvalResult {
12681 match v {
12682 Value::Fn(params, body, mut captured) => {
12683 for (p, a) in params.iter().zip(args) {
12684 captured.insert(p.clone(), a);
12685 }
12686 match self.framed("<closure>", |me| me.exec_block(&body, &mut captured)) {
12687 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
12688 Err(EvalErr::Return(v)) => Ok(v),
12689 Err(e) => Err(e),
12690 }
12691 },
12692 other => Err(EvalErr::from(format!("cannot call {:?}", other))),
12693 }
12694 }
12695
12696 fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
12697 match (&recv, method) {
12698 (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
12699 (Value::Str(s), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(s.len() as f64)),
12706 (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
12707 (Value::Str(s), "contains" | "包含") => {
12708 if let Some(Value::Str(sub)) = args.first() {
12709 Ok(Value::Bool(s.contains(sub.as_str())))
12710 } else {
12711 Ok(Value::Bool(false))
12712 }
12713 },
12714 (Value::Str(s), "push_str" | "推_文") => {
12715 let mut s2 = s.clone();
12716 if let Some(Value::Str(a)) = args.first() {
12717 s2.push_str(a);
12718 }
12719 Ok(Value::Str(s2))
12720 },
12721 (Value::List(v), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(v.len() as f64)),
12722 (Value::List(v), "push" | "推" | "添加" | "追加" | "추가" | "เพิ่ม") => {
12723 let mut v2: Vec<Value> = (**v).clone();
12724 if let Some(a) = args.first() {
12725 v2.push(a.clone());
12726 }
12727 Ok(Value::List(Rc::new(v2)))
12728 },
12729 (Value::Struct { fields, .. }, _) if args.is_empty() => fields
12731 .iter()
12732 .find(|(k, _)| k == method)
12733 .map(|(_, v)| v.clone())
12734 .ok_or_else(|| EvalErr::from(format!("no field '{method}' on {recv}"))),
12735 (Value::Variant { variant, .. }, "tag" | "标签" | "タグ" | "태그" | "ป้าย")
12737 if args.is_empty() =>
12738 {
12739 Ok(Value::Str(variant.clone()))
12740 },
12741 (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
12742 _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
12743 }
12744 }
12745
12746 fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
12749 match (pat, val) {
12750 (Pattern::Wildcard, _) => Some(new_env()),
12751 (Pattern::Str(s), Value::Str(v)) if s == v => Some(new_env()),
12752 (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(new_env()),
12753 (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(new_env()),
12754 (Pattern::Ident(name), _) => {
12755 let mut e = new_env();
12756 e.insert(name.clone(), val.clone());
12757 Some(e)
12758 },
12759 (Pattern::Constructor(ctor, inner_pat), _) => {
12760 let (matches, inner_val) = match (ctor.as_str(), val) {
12761 ("ok" | "好", Value::Ok(v)) => (true, Some(v.as_ref().clone())),
12762 ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
12763 ("ok" | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
12764 _ => (false, None),
12765 };
12766 if !matches {
12767 return None;
12768 }
12769 match (inner_pat, inner_val) {
12770 (Some(p), Some(v)) => self.match_pattern(p, &v),
12771 (None, _) => Some(new_env()),
12772 (Some(p), None) => self.match_pattern(p, &Value::Unit),
12773 }
12774 },
12775 (Pattern::Variant(vname, sub_pats), Value::Variant { variant, payload, .. }) => {
12777 if vname != variant || sub_pats.len() != payload.len() {
12778 return None;
12779 }
12780 let mut bindings = new_env();
12781 for (p, v) in sub_pats.iter().zip(payload.iter()) {
12782 bindings.extend(self.match_pattern(p, v)?);
12783 }
12784 Some(bindings)
12785 },
12786 (Pattern::Variant(vname, sub), Value::Ok(v)) if (vname == "ok" || vname == "好") => {
12789 match sub.as_slice() {
12790 [] => Some(new_env()),
12791 [p] => self.match_pattern(p, v),
12792 _ => None,
12793 }
12794 },
12795 (Pattern::Variant(vname, sub), Value::Err(v))
12796 if (vname == "bad" || vname == "坏" || vname == "err") =>
12797 {
12798 match sub.as_slice() {
12799 [] => Some(new_env()),
12800 [p] => self.match_pattern(p, v),
12801 _ => None,
12802 }
12803 },
12804 _ => None,
12805 }
12806 }
12807
12808 fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
12811 match val {
12812 Value::List(v) => Ok(Rc::try_unwrap(v).unwrap_or_else(|rc| (*rc).clone())),
12813 Value::Str(s) => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
12814 Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
12815 other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
12816 }
12817 }
12818
12819 pub(crate) fn is_truthy(&self, val: &Value) -> bool {
12820 match val {
12821 Value::Bool(b) => *b,
12822 Value::Unit => false,
12823 Value::Number(n) => *n != 0.0,
12824 Value::Str(s) => !s.is_empty(),
12825 Value::List(v) => !v.is_empty(),
12826 Value::Ok(_) => true,
12827 Value::Err(_) => false,
12828 Value::Fn(_, _, _) => true,
12829 Value::Struct { .. } => true,
12830 Value::Variant { .. } => true,
12831 }
12832 }
12833
12834 fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
12835 match val {
12836 Value::Number(n) => Ok(*n),
12837 Value::Str(s) => s
12838 .parse()
12839 .map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
12840 other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
12841 }
12842 }
12843
12844 fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
12846 match args.get(n) {
12847 Some(v) => self.to_number(v),
12848 None => Ok(default),
12849 }
12850 }
12851
12852 fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
12853 args.get(n)
12854 .map(|v| v.to_string())
12855 .unwrap_or_else(|| default.to_string())
12856 }
12857
12858 #[allow(dead_code)]
12860 fn arg_list_f32(&self, args: &[Value], n: usize) -> Vec<f32> {
12861 match args.get(n) {
12862 Some(Value::List(v)) => v
12863 .iter()
12864 .filter_map(|x| match x {
12865 Value::Number(n) => Some(*n as f32),
12866 _ => None,
12867 })
12868 .collect(),
12869 _ => Vec::new(),
12870 }
12871 }
12872
12873 #[cfg(not(target_arch = "wasm32"))]
12876 fn color_at(&self, args: &[Value], i: usize, default: u32) -> u32 {
12877 match (args.get(i), args.get(i + 1), args.get(i + 2)) {
12878 (Some(a), Some(b), Some(c)) => {
12879 match (self.to_number(a), self.to_number(b), self.to_number(c)) {
12880 (Ok(r), Ok(g), Ok(bl)) => {
12881 ((r as u32 & 0xFF) << 16) | ((g as u32 & 0xFF) << 8) | (bl as u32 & 0xFF)
12882 },
12883 _ => default,
12884 }
12885 },
12886 _ => default,
12887 }
12888 }
12889
12890 #[cfg(not(target_arch = "wasm32"))]
12892 fn pitch_arg(&self, args: &[Value], i: usize, default: i32) -> i32 {
12893 match args.get(i) {
12894 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(default),
12895 Some(Value::Number(n)) => *n as i32,
12896 _ => default,
12897 }
12898 }
12899
12900 #[cfg(not(target_arch = "wasm32"))]
12902 fn mouse_now(&self) -> (f32, f32, bool) {
12903 let gfx = self.gfx.borrow();
12904 let (mx, my) = gfx
12905 .window
12906 .as_ref()
12907 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
12908 .unwrap_or((0.0, 0.0));
12909 let down = gfx
12910 .window
12911 .as_ref()
12912 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
12913 .unwrap_or(false);
12914 (mx, my, down)
12915 }
12916
12917 #[cfg(not(target_arch = "wasm32"))]
12921 fn draw_ui(&self, d: &ling_ui::widgets::Draw) {
12922 let mut gfx = self.gfx.borrow_mut();
12923 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
12924 for (c, poly) in &d.fills {
12925 crate::gfx::raster::fill_contours_aa(
12926 &mut gfx.buffer,
12927 w,
12928 h,
12929 *c,
12930 add,
12931 std::slice::from_ref(poly),
12932 );
12933 }
12934 for (c, pl) in &d.strokes {
12935 for s in pl.windows(2) {
12936 crate::gfx::raster::draw_line_aa(
12937 &mut gfx.buffer,
12938 w,
12939 h,
12940 *c,
12941 add,
12942 s[0][0],
12943 s[0][1],
12944 s[1][0],
12945 s[1][1],
12946 );
12947 }
12948 }
12949 }
12950
12951 fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
12953 let tx = self.arg_num(args, 0, 0.0)? as usize;
12954 let ty = self.arg_num(args, 1, 0.0)? as usize;
12955 let tw = self.arg_num(args, 2, 256.0)? as usize;
12956 let th = self.arg_num(args, 3, 256.0)? as usize;
12957 Ok((tx, ty, tw.max(1), th.max(1)))
12958 }
12959
12960 pub(crate) fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
12961 match op {
12962 BinOp::Add => match (l, r) {
12963 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
12964 (Value::Str(a), Value::Str(b)) => Ok(Value::Str(a + &b)),
12965 (Value::Str(a), b) => Ok(Value::Str(a + &b.to_string())),
12966 (a, Value::Str(b)) => Ok(Value::Str(a.to_string() + &b)),
12967 (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
12968 },
12969 BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
12970 BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
12971 BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
12972 BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
12973 BinOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
12974 BinOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
12975 BinOp::Lt => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
12976 BinOp::Gt => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
12977 BinOp::Le => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
12978 BinOp::Ge => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
12979 BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
12980 BinOp::Or => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
12981 }
12982 }
12983
12984 fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
12985 if args.is_empty() {
12986 return Ok(String::new());
12987 }
12988 let fmt = match &args[0] {
12989 Value::Str(s) => s.clone(),
12990 other => return Ok(other.to_string()),
12991 };
12992
12993 let mut result = String::new();
12994 let mut arg_idx = 1usize;
12995 let mut chars = fmt.chars().peekable();
12996 while let Some(c) = chars.next() {
12997 if c == '{' {
12998 if chars.peek() == Some(&'}') {
12999 chars.next();
13000 if arg_idx < args.len() {
13001 result.push_str(&args[arg_idx].to_string());
13002 arg_idx += 1;
13003 }
13004 } else {
13005 let mut spec = String::new();
13006 for ch in chars.by_ref() {
13007 if ch == '}' {
13008 break;
13009 }
13010 spec.push(ch);
13011 }
13012 if arg_idx < args.len() {
13013 if let Some(suffix) = spec.strip_prefix(":.") {
13014 if let Value::Number(n) = &args[arg_idx] {
13015 let prec: usize =
13016 suffix.trim_end_matches('f').parse().unwrap_or(2);
13017 result.push_str(&format!("{:.prec$}", n));
13018 arg_idx += 1;
13019 continue;
13020 }
13021 }
13022 result.push_str(&args[arg_idx].to_string());
13023 arg_idx += 1;
13024 }
13025 }
13026 } else {
13027 result.push(c);
13028 }
13029 }
13030 Ok(result)
13031 }
13032}
13033
13034#[cfg(not(target_arch = "wasm32"))]
13035#[cfg(not(target_arch = "wasm32"))]
13037fn parse_pad_button(name: &str) -> Option<ling_input::GamepadButton> {
13038 use ling_input::GamepadButton as B;
13039 Some(match name.to_ascii_lowercase().as_str() {
13040 "a" | "south" | "cross" => B::South,
13041 "b" | "east" | "circle" => B::East,
13042 "x" | "west" | "square" => B::West,
13043 "y" | "north" | "triangle" => B::North,
13044 "lb" | "l1" | "left_shoulder" => B::LeftShoulder,
13045 "rb" | "r1" | "right_shoulder" => B::RightShoulder,
13046 "lt" | "l2" | "left_trigger" => B::LeftTrigger,
13047 "rt" | "r2" | "right_trigger" => B::RightTrigger,
13048 "start" | "menu" | "options" | "démarrer" | "начать" => B::Start,
13049 "select" | "back" | "share" | "view" => B::Select,
13050 "guide" | "home" => B::Guide,
13051 "l3" | "left_stick" => B::LeftStick,
13052 "r3" | "right_stick" => B::RightStick,
13053 "up" | "dpad_up" => B::DpadUp,
13054 "down" | "dpad_down" => B::DpadDown,
13055 "left" | "dpad_left" => B::DpadLeft,
13056 "right" | "dpad_right" => B::DpadRight,
13057 _ => return None,
13058 })
13059}
13060
13061#[cfg(not(target_arch = "wasm32"))]
13062fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
13063 use minifb::Key;
13064 Some(match name {
13065 "numpad0" | "kp0" => Key::NumPad0,
13066 "numpad1" | "kp1" => Key::NumPad1,
13067 "numpad2" | "kp2" => Key::NumPad2,
13068 "numpad3" | "kp3" => Key::NumPad3,
13069 "numpad4" | "kp4" => Key::NumPad4,
13070 "numpad5" | "kp5" => Key::NumPad5,
13071 "numpad6" | "kp6" => Key::NumPad6,
13072 "numpad7" | "kp7" => Key::NumPad7,
13073 "numpad8" | "kp8" => Key::NumPad8,
13074 "numpad9" | "kp9" => Key::NumPad9,
13075 "numpad+" | "kp+" => Key::NumPadPlus,
13076 "numpad-" | "kp-" => Key::NumPadMinus,
13077 "numpad*" | "kp*" => Key::NumPadAsterisk,
13078 "numpad/" | "kp/" => Key::NumPadSlash,
13079 "left" => Key::Left,
13080 "right" => Key::Right,
13081 "up" => Key::Up,
13082 "down" => Key::Down,
13083 "space" => Key::Space,
13084 "enter" => Key::Enter,
13085 "escape" => Key::Escape,
13086 "pageup" => Key::PageUp,
13087 "pagedown" => Key::PageDown,
13088 "lshift" | "leftshift" => Key::LeftShift,
13089 "rshift" | "rightshift" => Key::RightShift,
13090 "lctrl" | "leftctrl" => Key::LeftCtrl,
13091 "rctrl" | "rightctrl" => Key::RightCtrl,
13092 "lalt" | "leftalt" => Key::LeftAlt,
13093 "ralt" | "rightalt" => Key::RightAlt,
13094 "tab" => Key::Tab,
13095 "backspace" => Key::Backspace,
13096 "delete" => Key::Delete,
13097 "insert" => Key::Insert,
13098 "home" => Key::Home,
13099 "end" => Key::End,
13100 "a" => Key::A,
13101 "b" => Key::B,
13102 "c" => Key::C,
13103 "d" => Key::D,
13104 "e" => Key::E,
13105 "f" => Key::F,
13106 "g" => Key::G,
13107 "h" => Key::H,
13108 "i" => Key::I,
13109 "j" => Key::J,
13110 "k" => Key::K,
13111 "l" => Key::L,
13112 "m" => Key::M,
13113 "n" => Key::N,
13114 "o" => Key::O,
13115 "p" => Key::P,
13116 "q" => Key::Q,
13117 "r" => Key::R,
13118 "s" => Key::S,
13119 "t" => Key::T,
13120 "u" => Key::U,
13121 "v" => Key::V,
13122 "w" => Key::W,
13123 "x" => Key::X,
13124 "y" => Key::Y,
13125 "z" => Key::Z,
13126 "0" => Key::Key0,
13127 "1" => Key::Key1,
13128 "2" => Key::Key2,
13129 "3" => Key::Key3,
13130 "4" => Key::Key4,
13131 "5" => Key::Key5,
13132 "6" => Key::Key6,
13133 "7" => Key::Key7,
13134 "8" => Key::Key8,
13135 "9" => Key::Key9,
13136 _ => return None,
13137 })
13138}
13139
13140pub(crate) fn values_equal(a: &Value, b: &Value) -> bool {
13141 match (a, b) {
13142 (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
13143 (Value::Str(x), Value::Str(y)) => x == y,
13144 (Value::Bool(x), Value::Bool(y)) => x == y,
13145 (Value::Unit, Value::Unit) => true,
13146 _ => false,
13147 }
13148}
13149
13150#[cfg(all(not(target_arch = "wasm32"), windows))]
13159fn make_borderless_fullscreen(hwnd: isize, screen_w: i32, screen_h: i32) {
13160 if hwnd == 0 {
13161 return;
13162 }
13163 unsafe {
13164 extern "system" {
13165 fn SetWindowLongPtrW(hwnd: isize, index: i32, new: isize) -> isize;
13166 fn SetWindowPos(
13167 hwnd: isize,
13168 insert_after: isize,
13169 x: i32,
13170 y: i32,
13171 cx: i32,
13172 cy: i32,
13173 flags: u32,
13174 ) -> i32;
13175 fn ShowWindow(hwnd: isize, cmd: i32) -> i32;
13176 }
13177 const GWL_STYLE: i32 = -16;
13178 const GWL_EXSTYLE: i32 = -20;
13179 SetWindowLongPtrW(hwnd, GWL_STYLE, 0x9000_0000isize);
13182 SetWindowLongPtrW(hwnd, GWL_EXSTYLE, 0);
13184 SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0020 | 0x0040);
13186 ShowWindow(hwnd, 3); }
13188}
13189
13190#[cfg(all(not(target_arch = "wasm32"), windows))]
13204fn force_window_focus(hwnd: isize) {
13205 if hwnd == 0 {
13206 return;
13207 }
13208 unsafe {
13209 extern "system" {
13210 fn GetForegroundWindow() -> isize;
13211 fn GetWindowThreadProcessId(hwnd: isize, pid: *mut u32) -> u32;
13212 fn GetCurrentThreadId() -> u32;
13213 fn AttachThreadInput(id_attach: u32, id_attach_to: u32, attach: i32) -> i32;
13214 fn SetForegroundWindow(hwnd: isize) -> i32;
13215 fn BringWindowToTop(hwnd: isize) -> i32;
13216 fn SetFocus(hwnd: isize) -> isize;
13217 fn SetActiveWindow(hwnd: isize) -> isize;
13218 }
13219 let fg = GetForegroundWindow();
13220 if fg != 0 && fg != hwnd {
13221 let mut fg_pid: u32 = 0;
13222 let fg_tid = GetWindowThreadProcessId(fg, &mut fg_pid);
13223 let my_tid = GetCurrentThreadId();
13224 if fg_tid != 0 && fg_tid != my_tid {
13225 AttachThreadInput(my_tid, fg_tid, 1);
13226 SetForegroundWindow(hwnd);
13227 BringWindowToTop(hwnd);
13228 SetFocus(hwnd);
13229 SetActiveWindow(hwnd);
13230 AttachThreadInput(my_tid, fg_tid, 0);
13231 return;
13232 }
13233 }
13234 SetForegroundWindow(hwnd);
13235 BringWindowToTop(hwnd);
13236 SetFocus(hwnd);
13237 SetActiveWindow(hwnd);
13238 }
13239}
13240
13241#[cfg(all(not(target_arch = "wasm32"), windows))]
13246fn set_window_topmost(hwnd: isize, topmost: bool) {
13247 if hwnd == 0 {
13248 return;
13249 }
13250 unsafe {
13251 extern "system" {
13252 fn SetWindowPos(
13253 hwnd: isize,
13254 insert_after: isize,
13255 x: i32,
13256 y: i32,
13257 cx: i32,
13258 cy: i32,
13259 flags: u32,
13260 ) -> i32;
13261 }
13262 let insert_after: isize = if topmost { -1 } else { -2 }; SetWindowPos(hwnd, insert_after, 0, 0, 0, 0, 0x0002 | 0x0001 | 0x0010);
13266 }
13267}
13268
13269#[cfg(not(target_arch = "wasm32"))]
13275fn apply_frame_pacing(win: &mut minifb::Window, vsync: bool) {
13276 match std::env::var("LING_FPS_CAP")
13277 .ok()
13278 .and_then(|v| v.parse::<usize>().ok())
13279 {
13280 Some(0) => win.set_target_fps(100_000),
13281 Some(cap) => win.set_target_fps(cap),
13282 None if vsync => win.set_target_fps(monitor_info().2.max(30) as usize),
13283 None => win.set_target_fps(100_000),
13284 }
13285}
13286
13287#[cfg(all(not(target_arch = "wasm32"), windows))]
13290fn monitor_info() -> (i32, i32, i32) {
13291 unsafe {
13292 extern "system" {
13293 fn GetSystemMetrics(index: i32) -> i32;
13294 fn GetDC(hwnd: isize) -> isize;
13295 fn ReleaseDC(hwnd: isize, hdc: isize) -> i32;
13296 fn GetDeviceCaps(hdc: isize, index: i32) -> i32;
13297 }
13298 let w = GetSystemMetrics(0).max(1); let h = GetSystemMetrics(1).max(1); let hdc = GetDC(0);
13301 let mut hz = if hdc != 0 { GetDeviceCaps(hdc, 116) } else { 0 }; if hdc != 0 {
13303 ReleaseDC(0, hdc);
13304 }
13305 if hz <= 1 {
13306 hz = 60; }
13308 (w, h, hz)
13309 }
13310}
13311
13312#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13316fn monitor_info() -> (i32, i32, i32) {
13317 let (w, h) = native_screen_size();
13318 (w as i32, h as i32, linux_refresh_hz().unwrap_or(60))
13319}
13320
13321#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13326fn linux_refresh_hz() -> Option<i32> {
13327 let out = std::process::Command::new("xrandr")
13328 .arg("--current")
13329 .output()
13330 .ok()?;
13331 if !out.status.success() {
13332 return None;
13333 }
13334 parse_xrandr_max_hz(&String::from_utf8_lossy(&out.stdout))
13335}
13336
13337#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13340fn parse_xrandr_max_hz(text: &str) -> Option<i32> {
13341 text.split_whitespace()
13342 .filter(|tok| tok.contains('*'))
13343 .filter_map(|tok| {
13344 tok.trim_matches(|c: char| !c.is_ascii_digit() && c != '.')
13345 .parse::<f64>()
13346 .ok()
13347 })
13348 .map(|hz| hz.round() as i32)
13349 .filter(|&hz| (24..=1000).contains(&hz))
13350 .max()
13351}
13352
13353#[cfg(target_arch = "wasm32")]
13355fn monitor_info() -> (i32, i32, i32) {
13356 let (w, h) = crate::gfx::webgl::canvas_size();
13357 (w as i32, h as i32, 60)
13358}
13359
13360#[cfg(all(test, not(target_arch = "wasm32"), not(windows)))]
13361mod xrandr_tests {
13362 use super::parse_xrandr_max_hz;
13363
13364 #[test]
13365 fn picks_highest_active_output() {
13366 let text = "\
13367eDP-1 connected primary 1920x1080+0+0
13368 1920x1080 60.00*+ 59.94
13369DP-1 connected 2560x1440+1920+0
13370 2560x1440 144.00*+ 120.00 60.00
13371";
13372 assert_eq!(parse_xrandr_max_hz(text), Some(144));
13373 }
13374
13375 #[test]
13376 fn single_output() {
13377 let text = " 1920x1080 75.00*+ 60.00\n";
13378 assert_eq!(parse_xrandr_max_hz(text), Some(75));
13379 }
13380
13381 #[test]
13382 fn no_active_mode_returns_none() {
13383 let text = "eDP-1 disconnected\n";
13384 assert_eq!(parse_xrandr_max_hz(text), None);
13385 }
13386
13387 #[test]
13388 fn out_of_range_hz_filtered() {
13389 let text = " 1x1 5000.00*+\n";
13390 assert_eq!(parse_xrandr_max_hz(text), None);
13391 }
13392}
13393
13394#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13397fn native_screen_size() -> (f64, f64) {
13398 (1920.0, 1080.0)
13401}
13402
13403struct LingProfileState {
13415 enabled: bool,
13416 every: u64,
13417 frames: u64,
13418 calls: std::collections::HashMap<String, (u64, u128)>, }
13420
13421thread_local! {
13422 static LING_PROFILE: std::cell::RefCell<LingProfileState> = std::cell::RefCell::new({
13423 let enabled = std::env::var("LING_PROFILE").map(|v| v != "0" && !v.is_empty()).unwrap_or(false);
13424 let every = std::env::var("LING_PROFILE_EVERY").ok()
13425 .and_then(|v| v.parse::<u64>().ok()).filter(|&n| n > 0).unwrap_or(240);
13426 if enabled {
13427 eprintln!("[ling-profile] ON — report every {every} frames (set LING_PROFILE_EVERY to change)");
13428 }
13429 LingProfileState { enabled, every, frames: 0, calls: std::collections::HashMap::new() }
13430 });
13431}
13432
13433#[inline]
13434fn ling_profile_enabled() -> bool {
13435 LING_PROFILE.with(|p| p.borrow().enabled)
13436}
13437
13438thread_local! {
13439 static LING_FPS: std::cell::RefCell<(bool, f64, u32, f64)> = std::cell::RefCell::new(
13440 (std::env::var("LING_FPS").map(|v| v != "0" && !v.is_empty()).unwrap_or(false), 0.0, 0, 0.0)
13441 );
13442}
13443
13444#[cfg(not(target_arch = "wasm32"))]
13445fn ling_fps_tick() {
13446 LING_FPS.with(|s| {
13447 let mut s = s.borrow_mut();
13448 if !s.0 {
13449 return;
13450 }
13451 let now = crate::runtime::now_secs();
13452 if s.1 > 0.0 {
13453 s.3 += now - s.1;
13454 s.2 += 1;
13455 if s.2 >= 120 {
13456 let avg = s.3 / s.2 as f64;
13457 eprintln!(
13458 "[fps] {:.1} fps ({:.2} ms/frame, wall, {} frames)",
13459 1.0 / avg,
13460 avg * 1000.0,
13461 s.2
13462 );
13463 s.2 = 0;
13464 s.3 = 0.0;
13465 }
13466 }
13467 s.1 = now;
13468 });
13469}
13470
13471thread_local! {
13476 static LING_PHASE: std::cell::RefCell<(bool, u64, [u128; 5])> = std::cell::RefCell::new(
13477 (std::env::var_os("LING_PHASE").is_some(), 0, [0; 5])
13478 );
13479}
13480
13481pub mod phase {
13483 pub const FLUSH: usize = 0;
13484 pub const TOON: usize = 1;
13485 pub const BLIT: usize = 2;
13486 pub const DISTORT: usize = 3;
13487 pub const SORT: usize = 4;
13488}
13489
13490#[cfg(not(target_arch = "wasm32"))]
13491#[inline]
13492pub fn ling_phase_add(idx: usize, nanos: u128) {
13493 LING_PHASE.with(|p| {
13494 let mut p = p.borrow_mut();
13495 if p.0 {
13496 p.2[idx] += nanos;
13497 }
13498 });
13499}
13500
13501#[cfg(not(target_arch = "wasm32"))]
13502fn ling_phase_frame() {
13503 LING_PHASE.with(|p| {
13504 let mut p = p.borrow_mut();
13505 if !p.0 {
13506 return;
13507 }
13508 p.1 += 1;
13509 if p.1 >= 120 {
13510 let f = p.1 as f64;
13511 let ms = |i: usize| p.2[i] as f64 / 1e6 / f;
13512 eprintln!(
13513 "[phase] sort={:.2} flush={:.2} toon={:.2} blit={:.2} distort={:.2} ms/frame",
13514 ms(phase::SORT),
13515 ms(phase::FLUSH),
13516 ms(phase::TOON),
13517 ms(phase::BLIT),
13518 ms(phase::DISTORT)
13519 );
13520 p.1 = 0;
13521 p.2 = [0; 5];
13522 }
13523 });
13524}
13525
13526fn ling_profile_record(name: &str, nanos: u128) {
13527 let is_frame = matches!(
13529 name,
13530 "present" | "แสดงผล" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
13531 );
13532 LING_PROFILE.with(|p| {
13533 let mut p = p.borrow_mut();
13534 let e = p.calls.entry(name.to_string()).or_insert((0, 0));
13535 e.0 += 1;
13536 e.1 += nanos;
13537 if is_frame {
13538 p.frames += 1;
13539 if p.frames % p.every == 0 {
13540 ling_profile_print(&p);
13541 }
13542 }
13543 });
13544}
13545
13546fn ling_profile_print(p: &LingProfileState) {
13547 let mut rows: Vec<(&String, u64, u128)> =
13548 p.calls.iter().map(|(n, (c, ns))| (n, *c, *ns)).collect();
13549 use std::cmp::Reverse;
13550 rows.sort_by_key(|x| Reverse(x.2)); let total_ns: u128 = p.calls.values().map(|(_, ns)| *ns).sum();
13552 let total_calls: u64 = p.calls.values().map(|(c, _)| *c).sum();
13553 let fr = p.frames.max(1) as f64;
13554 eprintln!(
13555 "\n┌─ LING PROFILE ── frames={} ─ builtin calls by total inclusive time ─────────────",
13556 p.frames
13557 );
13558 eprintln!(
13559 "│ {:<24} {:>9} {:>9} {:>10} {:>9} {:>6}",
13560 "builtin", "calls", "calls/fr", "total_ms", "ms/frame", "%time"
13561 );
13562 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13563 for (name, count, ns) in rows.iter().take(30) {
13564 let ms = *ns as f64 / 1e6;
13565 let pct = if total_ns > 0 {
13566 *ns as f64 / total_ns as f64 * 100.0
13567 } else {
13568 0.0
13569 };
13570 eprintln!(
13571 "│ {:<24} {:>9} {:>9.1} {:>10.1} {:>9.3} {:>5.1}%",
13572 truncate_name(name),
13573 count,
13574 *count as f64 / fr,
13575 ms,
13576 ms / fr,
13577 pct
13578 );
13579 }
13580 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13581 eprintln!(
13582 "│ TOTAL {} builtin calls, {:.1} ms over {} frames → {:.0} calls/frame, {:.2} ms/frame in builtins",
13583 total_calls,
13584 total_ns as f64 / 1e6,
13585 p.frames,
13586 total_calls as f64 / fr,
13587 total_ns as f64 / 1e6 / fr
13588 );
13589 eprintln!("└──────────────────────────────────────────────────────────────────────────────");
13590}
13591
13592fn truncate_name(s: &str) -> String {
13595 let max = 24;
13596 if s.chars().count() <= max {
13597 s.to_string()
13598 } else {
13599 let mut t: String = s.chars().take(max - 1).collect();
13600 t.push('…');
13601 t
13602 }
13603}