1#[cfg(not(target_arch = "wasm32"))]
3mod ai;
4#[cfg(not(target_arch = "wasm32"))]
5mod gamepad;
6#[cfg(target_arch = "wasm32")]
7mod input_web;
8#[cfg(not(target_arch = "wasm32"))]
9pub(crate) mod jit_abi;
10
11#[cfg(not(target_arch = "wasm32"))]
14pub fn init_aot_runtime() {
15 let interp = Interpreter::new();
16 jit_abi::init(interp);
17}
18
19pub fn now_secs() -> f64 {
22 #[cfg(target_arch = "wasm32")]
23 {
24 js_sys::Date::now() / 1000.0
25 }
26 #[cfg(not(target_arch = "wasm32"))]
27 {
28 std::time::SystemTime::now()
29 .duration_since(std::time::UNIX_EPOCH)
30 .map(|d| d.as_secs_f64())
31 .unwrap_or(0.0)
32 }
33}
34
35static LINE_HUE_RATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
39static LINE_HUE_START: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
40
41pub fn set_line_hue_rate(rate: f64) {
43 LINE_HUE_RATE.store(rate.to_bits(), std::sync::atomic::Ordering::Relaxed);
44 LINE_HUE_START.store(now_secs().to_bits(), std::sync::atomic::Ordering::Relaxed);
45}
46
47pub fn line_hue_phase() -> Option<f64> {
51 let rate = f64::from_bits(LINE_HUE_RATE.load(std::sync::atomic::Ordering::Relaxed));
52 if rate <= 0.0 {
53 return None;
54 }
55 let start = f64::from_bits(LINE_HUE_START.load(std::sync::atomic::Ordering::Relaxed));
56 Some((now_secs() - start) * rate)
57}
58
59#[cfg(target_arch = "wasm32")]
62thread_local! {
63 static WASM_MODULES: std::cell::RefCell<std::collections::HashMap<String, String>> =
64 std::cell::RefCell::new(std::collections::HashMap::new());
65}
66
67#[cfg(target_arch = "wasm32")]
70pub fn register_wasm_module(path: &str, source: &str) {
71 WASM_MODULES.with(|m| m.borrow_mut().insert(path.to_string(), source.to_string()));
72}
73
74#[cfg(target_arch = "wasm32")]
76pub(crate) fn get_wasm_module(path: &str) -> Option<String> {
77 WASM_MODULES.with(|m| m.borrow().get(path).cloned())
78}
79
80#[cfg(target_arch = "wasm32")]
81#[inline]
82fn wasm_sleep_ms(ms: i32) {
83 if ms <= 0 {
84 return;
85 }
86
87 let global = js_sys::global();
90 let has_sab = js_sys::Reflect::has(
91 &global,
92 &wasm_bindgen::JsValue::from_str("SharedArrayBuffer"),
93 )
94 .unwrap_or(false);
95 let has_atomics =
96 js_sys::Reflect::has(&global, &wasm_bindgen::JsValue::from_str("Atomics")).unwrap_or(false);
97 if has_sab && has_atomics {
98 let sab = js_sys::SharedArrayBuffer::new(4);
99 let i32a = js_sys::Int32Array::new(&sab);
100 if js_sys::Atomics::wait_with_timeout(&i32a, 0, 0, ms as f64).is_ok() {
101 return;
102 }
103 }
104
105 let end = js_sys::Date::now() + ms as f64;
106 while js_sys::Date::now() < end {}
107}
108
109#[cfg(target_arch = "wasm32")]
110fn wasm_fetch_sync(
111 path: &str,
112 response_type: &str,
113 return_expr: &str,
114) -> Result<wasm_bindgen::JsValue, String> {
115 let quoted = js_sys::JSON::stringify(&wasm_bindgen::JsValue::from_str(path))
116 .ok()
117 .and_then(|s| s.as_string())
118 .unwrap_or_else(|| "\"\"".to_string());
119
120 let script = format!(
121 "(function(){{\n var xhr = new XMLHttpRequest();\n xhr.open('GET', {quoted}, false);\n xhr.responseType = '{response_type}';\n xhr.send(null);\n if ((xhr.status|0) !== 200 && (xhr.status|0) !== 0) {{ throw new Error('HTTP ' + xhr.status + ' for ' + {quoted}); }}\n return {return_expr};\n}})()"
122 );
123
124 js_sys::eval(&script).map_err(|e| {
125 e.as_string()
126 .unwrap_or_else(|| format!("JS eval failed: {:?}", e))
127 })
128}
129
130#[cfg(target_arch = "wasm32")]
131fn wasm_fetch_bytes(path: &str) -> Result<Vec<u8>, String> {
132 let value = wasm_fetch_sync(
133 path,
134 "arraybuffer",
135 "new Uint8Array(xhr.response || new ArrayBuffer(0))",
136 )?;
137 let arr = js_sys::Uint8Array::new(&value);
138 let mut out = vec![0u8; arr.length() as usize];
139 arr.copy_to(&mut out);
140 Ok(out)
141}
142
143#[cfg(target_arch = "wasm32")]
144fn wasm_fetch_text(path: &str) -> Result<String, String> {
145 let value = wasm_fetch_sync(path, "text", "String(xhr.responseText || '')")?;
146 Ok(value.as_string().unwrap_or_default())
147}
148
149#[cfg(not(target_arch = "wasm32"))]
150mod net;
151#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
152pub mod web;
153use crate::gfx::{GfxState, Light};
154use crate::parser::ast::*;
155#[cfg(target_arch = "wasm32")]
156use js_sys;
157use std::cell::RefCell;
158use std::collections::HashMap;
159use std::rc::Rc;
160use crate::gfx::raster::draw_line;
163#[cfg(not(target_arch = "wasm32"))]
164use crate::gfx::raster::fill_triangle;
165#[cfg(not(target_arch = "wasm32"))]
166use ling_audio::{AudioEngine, ToneParams, Wave};
167
168#[cfg(not(target_arch = "wasm32"))]
169use ling_audio::FftAnalyzer;
170
171#[cfg(not(target_arch = "wasm32"))]
172use ling_mic;
173
174#[derive(Debug, Clone)]
177pub enum Value {
178 Str(String),
179 Number(f64),
180 Bool(bool),
181 Unit,
182 List(Rc<Vec<Value>>),
183 Ok(Box<Value>),
184 Err(Box<Value>),
185 Fn(Vec<String>, Vec<Stmt>, Env),
186 Struct {
188 name: String,
189 fields: Vec<(String, Value)>,
190 },
191 Variant {
193 enum_name: String,
194 variant: String,
195 payload: Vec<Value>,
196 },
197}
198
199use rustc_hash::FxHashMap;
202type Env = FxHashMap<String, Value>;
203
204#[inline]
205fn new_env() -> Env {
206 FxHashMap::default()
207}
208
209impl std::fmt::Display for Value {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 match self {
212 Value::Str(s) => write!(f, "{s}"),
213 Value::Number(n) => {
214 if n.fract() == 0.0 && n.abs() < 1e15 {
215 write!(f, "{}", *n as i64)
216 } else {
217 write!(f, "{n}")
218 }
219 },
220 Value::Bool(b) => write!(f, "{b}"),
221 Value::Unit => write!(f, "()"),
222 Value::List(v) => {
223 write!(f, "[")?;
224 for (i, x) in v.iter().enumerate() {
225 if i > 0 {
226 write!(f, ", ")?;
227 }
228 write!(f, "{x}")?;
229 }
230 write!(f, "]")
231 },
232 Value::Ok(v) => write!(f, "Ok({v})"),
233 Value::Err(v) => write!(f, "Err({v})"),
234 Value::Fn(_, _, _) => write!(f, "<fn>"),
235 Value::Struct { name, fields } => {
236 write!(f, "{name} {{ ")?;
237 for (i, (k, v)) in fields.iter().enumerate() {
238 if i > 0 {
239 write!(f, ", ")?;
240 }
241 write!(f, "{k}: {v}")?;
242 }
243 write!(f, " }}")
244 },
245 Value::Variant { variant, payload, .. } => {
246 write!(f, "{variant}")?;
247 if !payload.is_empty() {
248 write!(f, "(")?;
249 for (i, v) in payload.iter().enumerate() {
250 if i > 0 {
251 write!(f, ", ")?;
252 }
253 write!(f, "{v}")?;
254 }
255 write!(f, ")")?;
256 }
257 Ok(())
258 },
259 }
260 }
261}
262
263#[cfg(target_arch = "wasm32")]
264fn wasm_unsupported_builtin(name: &str) -> Option<Value> {
265 Some(match name {
266 "audio_blip"
268 | "提示音"
269 | "ビープ音"
270 | "효과음"
271 | "เสียงบี๊บ"
272 | "ui_sound"
273 | "界面音"
274 | "UI音"
275 | "인터페이스음"
276 | "เสียงปุ่ม"
277 | "audio_stop_sfx"
278 | "停止音效"
279 | "効果音停止"
280 | "효과음정지"
281 | "หยุดเอฟเฟกต์ทั้งหมด" | "بوق_کوتاه" | "نغمة_قصيرة" | "ביפ" | "بلپ_آواز" => Value::Unit,
282
283 "music_load"
285 | "载入音乐"
286 | "音楽読込"
287 | "음악로드"
288 | "โหลดเพลง"
289 | "music_patch"
290 | "乐器音色"
291 | "音色読込"
292 | "악기패치"
293 | "แพตช์เครื่องดนตรี"
294 | "music_lrc"
295 | "载入歌词"
296 | "歌詞読込"
297 | "가사로드"
298 | "โหลดเนื้อเพลง"
299 | "music_midi_load"
300 | "载入MIDI"
301 | "MIDI読込"
302 | "미디로드"
303 | "โหลดมิดี" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" => Value::Number(-1.0),
304
305 "music_duration"
306 | "音乐时长"
307 | "音楽長さ"
308 | "음악길이"
309 | "ความยาวเพลง"
310 | "music_bpm"
311 | "节拍速度"
312 | "テンポ"
313 | "템포"
314 | "จังหวะต่อนาที"
315 | "music_pos"
316 | "音乐位置"
317 | "音楽位置"
318 | "음악위치"
319 | "ตำแหน่งเพลง"
320 | "music_mic_pitch"
321 | "麦克风音高"
322 | "マイク音程"
323 | "마이크음정"
324 | "ระดับเสียงไมค์"
325 | "music_hz"
326 | "音符频率"
327 | "音符周波数"
328 | "음표주파수"
329 | "ความถี่โน้ต"
330 | "music_pitch_score"
331 | "音准评分"
332 | "音程スコア"
333 | "음정점수"
334 | "คะแนนเสียง"
335 | "music_judge"
336 | "判定"
337 | "判定する"
338 | "판정"
339 | "ตัดสินจังหวะ"
340 | "music_midi_count"
341 | "MIDI数量"
342 | "MIDI数"
343 | "미디수"
344 | "จำนวนมิดี" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" => Value::Number(0.0),
345
346 "music_key"
347 | "调性"
348 | "調性"
349 | "조성"
350 | "คีย์เพลง"
351 | "music_lyric"
352 | "当前歌词"
353 | "現在歌詞"
354 | "현재가사"
355 | "เนื้อเพลงปัจจุบัน"
356 | "music_note_name"
357 | "音名"
358 | "音名称"
359 | "음이름"
360 | "ชื่อโน้ต"
361 | "music_grade_name"
362 | "判定名"
363 | "判定名称"
364 | "판정이름"
365 | "ชื่อการตัดสิน" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" => Value::Str(String::new()),
366
367 "music_onsets"
368 | "音符起点"
369 | "オンセット"
370 | "온셋"
371 | "จุดเริ่มเสียง"
372 | "music_beat_grid"
373 | "节拍网格"
374 | "ビートグリッド"
375 | "비트그리드"
376 | "กริดจังหวะ"
377 | "music_midi_notes"
378 | "MIDI音符"
379 | "MIDIノート"
380 | "미디음표"
381 | "โน้ตมิดี"
382 | "music_midi_bars"
383 | "MIDI音条"
384 | "MIDIバー"
385 | "미디바"
386 | "แท่งมิดี"
387 | "music_fft"
388 | "音乐频谱"
389 | "音楽スペクトル"
390 | "음악스펙트럼"
391 | "สเปกตรัมเพลง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" => Value::List(Vec::new().into()),
392
393 "music_play"
394 | "播放音乐"
395 | "音楽再生"
396 | "음악재생"
397 | "เล่นเพลง"
398 | "music_pause"
399 | "暂停音乐"
400 | "音楽一時停止"
401 | "음악일시정지"
402 | "หยุดเพลงชั่วคราว"
403 | "music_stop"
404 | "停止音乐"
405 | "音楽停止"
406 | "음악정지"
407 | "หยุดเพลง"
408 | "music_seek"
409 | "定位音乐"
410 | "音楽シーク"
411 | "음악탐색"
412 | "ค้นหาเพลง"
413 | "music_volume"
414 | "音乐音量"
415 | "音楽音量"
416 | "음악음량"
417 | "ระดับเพลง"
418 | "music_note"
419 | "弹音符"
420 | "音符演奏"
421 | "음표연주"
422 | "เล่นโน้ต"
423 | "music_note_on"
424 | "音符开始"
425 | "音符オン"
426 | "음표켜기"
427 | "โน้ตเริ่ม"
428 | "music_note_off"
429 | "音符结束"
430 | "音符オフ"
431 | "음표끄기"
432 | "โน้ตจบ" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" => Value::Unit,
433
434 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" | "مایع_جدید" | "سائل_جديد" | "נוזל_חדש" | "نیا_مائع" => {
436 Value::Number(0.0)
437 },
438 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" | "ترکیب_مایع" | "مزج_سائل" | "ערבוב_נוזל" | "مائع_ملاؤ" => {
439 Value::Number(0.0)
440 },
441 "liquid_set_colors"
442 | "液体颜色"
443 | "液体配色"
444 | "액체색상"
445 | "สีของเหลว"
446 | "liquid_splat"
447 | "液体注入"
448 | "液体追加"
449 | "액체분사"
450 | "หยดของเหลว"
451 | "liquid_gravity"
452 | "液体重力"
453 | "液体重力ベクトル"
454 | "액체중력"
455 | "แรงโน้มถ่วงเหลว"
456 | "liquid_step"
457 | "液体步进"
458 | "液体更新"
459 | "액체스텝"
460 | "ก้าวของเหลว"
461 | "liquid_step_all"
462 | "液体全步进"
463 | "液体全更新"
464 | "전체액체스텝"
465 | "ก้าวของเหลวทั้งหมด"
466 | "liquid_rainbow"
467 | "液体彩虹"
468 | "液体虹"
469 | "액체무지개"
470 | "ของเหลวสายรุ้ง"
471 | "liquid_draw"
472 | "绘制液体"
473 | "液体描画"
474 | "액체그리기"
475 | "วาดของเหลว"
476 | "liquid_draw_surface"
477 | "液体贴面"
478 | "液体曲面"
479 | "액체곡면"
480 | "ของเหลวบนพื้นผิว" | "تنظیم_رنگ_مایع" | "عيّن_ألوان_السائل" | "קבע_צבעי_נוזל" | "مائع_رنگ_مقرر_کرو" => Value::Unit,
481
482 "nn_new"
484 | "建神经网"
485 | "ニューラル作成"
486 | "신경망생성"
487 | "สร้างโครงข่าย"
488 | "nn_load"
489 | "载入网"
490 | "網読込"
491 | "신경망불러오기"
492 | "โหลดโครงข่าย" | "شبکه_جدید" | "شبكة_جديدة" | "רשת_חדשה" | "نئی_نیورل_نیٹ" => Value::Number(-1.0),
493 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" | "پیشروی_شبکه" | "تمرير_أمامي" | "העברה_קדימה" | "فارورڈ_پاس" => {
494 Value::List(Vec::new().into())
495 },
496 "nn_train"
497 | "训练网"
498 | "ニューラル学習"
499 | "신경망학습"
500 | "ฝึกโครงข่าย"
501 | "nn_dense"
502 | "密集层"
503 | "密層追加"
504 | "밀집층"
505 | "ชั้นหนาแน่น" | "آموزش_شبکه" | "درّب_الشبكة" | "אמן_רשת" | "نیٹ_ٹریننگ" => Value::Number(0.0),
506 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" | "ذخیره_شبکه" | "احفظ_الشبكة" | "שמור_רשת" | "نیٹ_محفوظ_کرو" => {
507 Value::Bool(false)
508 },
509
510 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" | "ساخت_درخت_رفتار" | "ابنِ_شجرة_السلوك" | "בנה_עץ_התנהגות" | "بی_ٹی_تعمیر" => {
512 Value::Number(-1.0)
513 },
514 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" | "تیک_درخت_رفتار" | "نبضة_شجرة_السلوك" | "טיק_עץ_התנהגות" | "بی_ٹی_ٹک" => {
515 Value::Str(String::new())
516 },
517 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" | "وضعیت_درخت_رفتار" | "حالة_شجرة_السلوك" | "סטטוס_עץ_התנהגות" | "بی_ٹی_حالت" => {
518 Value::Number(0.0)
519 },
520 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" | "تنظیم_واقعیت" | "عيّن_حقيقة" | "קבע_עובדה" | "بی_ٹی_سیٹ" => {
521 Value::Unit
522 },
523
524 "dialog_new"
526 | "建对话模型"
527 | "対話モデル作成"
528 | "대화모델생성"
529 | "สร้างโมเดลสนทนา"
530 | "dialog_load_model"
531 | "对话载模"
532 | "対話モデル読込"
533 | "대화모델불러오기"
534 | "โหลดโมเดลสนทนา"
535 | "dialog_train"
536 | "对话训练"
537 | "対話訓練"
538 | "대화훈련"
539 | "ฝึกสนทนา"
540 | "dialog_load"
541 | "对话载入"
542 | "対話読込"
543 | "대화불러오기"
544 | "โหลดชุดสนทนา" | "مدل_گفتگوی_جدید" | "نموذج_حوار_جديد" | "מודל_דיאלוג_חדש" | "نیا_مکالمہ_ماڈل" => Value::Number(-1.0),
545 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" | "بگو" | "قل" | "אמור" | "کہو" => {
546 Value::Str(String::new())
547 },
548 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" | "ذخیره_مدل_گفتگو" | "احفظ_نموذج_الحوار" | "שמור_מודל_דיאלוג" | "مکالمہ_ماڈل_محفوظ" => {
549 Value::Bool(false)
550 },
551 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" | "یادگیری_گفتگو" | "تعلّم_الحوار" | "למד_דיאלוג" | "مکالمہ_سیکھو" => {
552 Value::Unit
553 },
554
555 "net_connect"
557 | "联网"
558 | "ネット接続"
559 | "네트연결"
560 | "เชื่อมเน็ต"
561 | "net_listen"
562 | "监听"
563 | "待機"
564 | "리슨"
565 | "รอรับ"
566 | "net_send"
567 | "发送"
568 | "送信"
569 | "전송"
570 | "ส่ง" | "اتصال_شبکه" | "اتصل_بالشبكة" | "התחבר_לרשת" | "نیٹ_کنیکٹ" => Value::Number(-1.0),
571 "net_recv"
572 | "接收"
573 | "受信"
574 | "수신"
575 | "รับ"
576 | "net_status"
577 | "连接状态"
578 | "接続状態"
579 | "연결상태"
580 | "สถานะการเชื่อม" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" => Value::Str(String::new()),
581 "net_discover" | "发现" | "探索" | "검색" | "ค้นหาเครือข่าย" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" => {
582 Value::List(Vec::new().into())
583 },
584 "net_close"
585 | "断开"
586 | "切断"
587 | "연결종료"
588 | "ตัดเชื่อม"
589 | "net_test"
590 | "测连接"
591 | "接続テスト"
592 | "연결테스트"
593 | "ทดสอบเน็ต" | "بستن_شبکه" | "أغلق_الشبكة" | "סגור_רשת" | "نیٹ_بند" => Value::Number(0.0),
594
595 _ => Value::Unit,
597 })
598}
599
600#[derive(Debug)]
603pub(crate) enum EvalErr {
604 Runtime(String),
605 Return(Value),
606 #[allow(dead_code)] Break,
608}
609
610impl From<String> for EvalErr {
611 fn from(s: String) -> Self {
612 EvalErr::Runtime(s)
613 }
614}
615
616type EvalResult = Result<Value, EvalErr>;
617
618#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
620fn base32_encode(data: &[u8]) -> String {
621 const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
622 let mut out = String::new();
623 let mut buffer: u32 = 0;
624 let mut bits = 0u32;
625 for &b in data {
626 buffer = (buffer << 8) | b as u32;
627 bits += 8;
628 while bits >= 5 {
629 bits -= 5;
630 out.push(ALPHABET[((buffer >> bits) & 0x1f) as usize] as char);
631 }
632 }
633 if bits > 0 {
634 out.push(ALPHABET[((buffer << (5 - bits)) & 0x1f) as usize] as char);
635 }
636 out
637}
638
639#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
641fn base32_decode(s: &str) -> Option<Vec<u8>> {
642 let mut buffer: u32 = 0;
643 let mut bits = 0u32;
644 let mut out = Vec::new();
645 for c in s.chars() {
646 if c == '=' || c.is_whitespace() {
647 continue;
648 }
649 let v = match c.to_ascii_uppercase() {
650 'A'..='Z' => c.to_ascii_uppercase() as u32 - 'A' as u32,
651 '2'..='7' => c as u32 - '2' as u32 + 26,
652 _ => return None,
653 };
654 buffer = (buffer << 5) | v;
655 bits += 5;
656 if bits >= 8 {
657 bits -= 8;
658 out.push((buffer >> bits) as u8);
659 }
660 }
661 Some(out)
662}
663
664#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
666fn totp_code(secret_b32: &str, step: u64) -> Option<String> {
667 let key = base32_decode(secret_b32)?;
668 let msg = step.to_be_bytes();
669 let mac = hmac_sha1(&key, &msg);
670 let offset = (mac[19] & 0x0f) as usize;
671 let bin = ((mac[offset] as u32 & 0x7f) << 24)
672 | ((mac[offset + 1] as u32) << 16)
673 | ((mac[offset + 2] as u32) << 8)
674 | (mac[offset + 3] as u32);
675 Some(format!("{:06}", bin % 1_000_000))
676}
677
678#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
680fn totp_check(secret_b32: &str, code: &str) -> bool {
681 if code.len() != 6 || !code.bytes().all(|b| b.is_ascii_digit()) {
682 return false;
683 }
684 let now_step = (crate::runtime::now_secs() as u64) / 30;
685 for delta in [-1i64, 0, 1] {
686 let step = (now_step as i64 + delta) as u64;
687 if let Some(expected) = totp_code(secret_b32, step) {
688 if expected.as_bytes().ct_eq_str(code.as_bytes()) {
690 return true;
691 }
692 }
693 }
694 false
695}
696
697#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
699fn hmac_sha1(key: &[u8], msg: &[u8]) -> [u8; 20] {
700 use hmac::{Mac, SimpleHmac};
701 let mut mac = SimpleHmac::<sha1::Sha1>::new_from_slice(key).expect("hmac key");
702 mac.update(msg);
703 let out = mac.finalize().into_bytes();
704 let mut arr = [0u8; 20];
705 arr.copy_from_slice(&out);
706 arr
707}
708
709#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
711trait CtEqStr {
712 fn ct_eq_str(&self, other: &[u8]) -> bool;
713}
714#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
715impl CtEqStr for [u8] {
716 fn ct_eq_str(&self, other: &[u8]) -> bool {
717 if self.len() != other.len() {
718 return false;
719 }
720 let mut diff = 0u8;
721 for (a, b) in self.iter().zip(other.iter()) {
722 diff |= a ^ b;
723 }
724 diff == 0
725 }
726}
727
728fn url_decode(s: &str) -> String {
730 let bytes = s.as_bytes();
731 let mut out = Vec::with_capacity(bytes.len());
732 let mut i = 0;
733 while i < bytes.len() {
734 match bytes[i] {
735 b'+' => {
736 out.push(b' ');
737 i += 1;
738 },
739 b'%' if i + 2 < bytes.len() => {
740 let hi = (bytes[i + 1] as char).to_digit(16);
741 let lo = (bytes[i + 2] as char).to_digit(16);
742 if let (Some(h), Some(l)) = (hi, lo) {
743 out.push((h * 16 + l) as u8);
744 i += 3;
745 } else {
746 out.push(bytes[i]);
747 i += 1;
748 }
749 },
750 b => {
751 out.push(b);
752 i += 1;
753 },
754 }
755 }
756 String::from_utf8_lossy(&out).into_owned()
757}
758
759#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
761fn values_to_sql_params(args: &[Value]) -> Vec<ling_http::rusqlite::types::Value> {
762 use ling_http::rusqlite::types::Value as Sql;
763 args.iter()
764 .map(|v| match v {
765 Value::Number(n) if n.fract() == 0.0 && n.abs() < 9e15 => Sql::Integer(*n as i64),
766 Value::Number(n) => Sql::Real(*n),
767 Value::Bool(b) => Sql::Integer(*b as i64),
768 other => Sql::Text(other.to_string()),
769 })
770 .collect()
771}
772
773struct SvgWriter {
778 path: String,
779 width: f64,
780 height: f64,
781 elements: Vec<String>,
782}
783
784impl SvgWriter {
785 fn new(path: String, width: f64, height: f64) -> Self {
786 let bg = format!("<rect width=\"{width}\" height=\"{height}\" fill=\"#0a0a0a\"/>");
787 Self { path, width, height, elements: vec![bg] }
788 }
789
790 fn save(&self) -> std::io::Result<()> {
791 let w = self.width;
792 let h = self.height;
793 let mut out = format!(
794 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
795 <svg xmlns=\"http://www.w3.org/2000/svg\" \
796 width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {w} {h}\">\n"
797 );
798 for elem in &self.elements {
799 out.push_str(" ");
800 out.push_str(elem);
801 out.push('\n');
802 }
803 out.push_str("</svg>\n");
804 if let Some(parent) = std::path::Path::new(&self.path).parent() {
806 if !parent.as_os_str().is_empty() {
807 let _ = std::fs::create_dir_all(parent);
808 }
809 }
810 std::fs::write(&self.path, out.as_bytes())
811 }
812}
813
814fn hsl_to_hex(h: f64, s: f64, l: f64) -> String {
815 let s = s / 100.0;
816 let l = l / 100.0;
817 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
818 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
819 let m = l - c / 2.0;
820 let (r1, g1, b1) = if h < 60.0 {
821 (c, x, 0.0)
822 } else if h < 120.0 {
823 (x, c, 0.0)
824 } else if h < 180.0 {
825 (0.0, c, x)
826 } else if h < 240.0 {
827 (0.0, x, c)
828 } else if h < 300.0 {
829 (x, 0.0, c)
830 } else {
831 (c, 0.0, x)
832 };
833 let r = ((r1 + m) * 255.0).round() as u8;
834 let g = ((g1 + m) * 255.0).round() as u8;
835 let b = ((b1 + m) * 255.0).round() as u8;
836 format!("#{r:02x}{g:02x}{b:02x}")
837}
838
839fn tex_hash(x: i32, y: i32, seed: u32) -> f32 {
842 let mut h = (x as u32)
843 .wrapping_add((y as u32).wrapping_mul(2654435769))
844 .wrapping_add(seed.wrapping_mul(1234567891));
845 h ^= h >> 16;
846 h = h.wrapping_mul(0x45d9f3b);
847 h ^= h >> 16;
848 h as f32 / u32::MAX as f32
849}
850
851fn tex_vnoise(x: f32, y: f32, seed: u32) -> f32 {
852 let xi = x.floor() as i32;
853 let yi = y.floor() as i32;
854 let sm = |t: f32| t * t * (3.0 - 2.0 * t);
855 let xf = sm(x - xi as f32);
856 let yf = sm(y - yi as f32);
857 let a = tex_hash(xi, yi, seed);
858 let b = tex_hash(xi + 1, yi, seed);
859 let c = tex_hash(xi, yi + 1, seed);
860 let d = tex_hash(xi + 1, yi + 1, seed);
861 a + (b - a) * xf + (c - a) * yf + (a - b - c + d) * xf * yf
862}
863
864fn tex_fbm(x: f32, y: f32, octaves: u32, seed: u32) -> f32 {
865 let mut v = 0.0f32;
866 let mut amp = 0.5f32;
867 let mut f = 1.0f32;
868 for i in 0..octaves {
869 v += tex_vnoise(x * f, y * f, seed.wrapping_add(i * 7919)) * amp;
870 amp *= 0.5;
871 f *= 2.0;
872 }
873 v
874}
875
876fn tex_palette(name: &str, t: f32) -> [f32; 3] {
877 let (a, b, c, d): ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) = match name {
878 "fire" => (
879 [0.8, 0.4, 0.1],
880 [0.7, 0.3, 0.1],
881 [1.0, 0.5, 0.3],
882 [0.0, 0.5, 0.8],
883 ),
884 "ocean" => (
885 [0.1, 0.4, 0.7],
886 [0.3, 0.3, 0.4],
887 [0.8, 1.0, 0.5],
888 [0.3, 0.0, 0.6],
889 ),
890 "psychedelic" => (
891 [0.5, 0.5, 0.5],
892 [0.8, 0.8, 0.8],
893 [1.0, 1.3, 0.7],
894 [0.0, 0.15, 0.3],
895 ),
896 "neon" => (
897 [0.5, 0.5, 0.5],
898 [0.5, 0.5, 0.5],
899 [2.0, 1.0, 0.0],
900 [0.5, 0.2, 0.25],
901 ),
902 "forest" => (
903 [0.3, 0.5, 0.2],
904 [0.2, 0.3, 0.1],
905 [1.0, 0.5, 0.8],
906 [0.1, 0.3, 0.6],
907 ),
908 _ => (
909 [0.5, 0.5, 0.5],
910 [0.5, 0.5, 0.5],
911 [1.0, 1.0, 1.0],
912 [0.0, 0.333, 0.667],
913 ),
914 };
915 [0, 1, 2]
916 .map(|i| (a[i] + b[i] * (std::f32::consts::TAU * (c[i] * t + d[i])).cos()).clamp(0.0, 1.0))
917}
918
919#[cfg(not(target_arch = "wasm32"))]
921fn key_char(k: minifb::Key, shift: bool) -> Option<char> {
927 use minifb::Key::*;
928 let base = match k {
929 A => 'a',
930 B => 'b',
931 C => 'c',
932 D => 'd',
933 E => 'e',
934 F => 'f',
935 G => 'g',
936 H => 'h',
937 I => 'i',
938 J => 'j',
939 K => 'k',
940 L => 'l',
941 M => 'm',
942 N => 'n',
943 O => 'o',
944 P => 'p',
945 Q => 'q',
946 R => 'r',
947 S => 's',
948 T => 't',
949 U => 'u',
950 V => 'v',
951 W => 'w',
952 X => 'x',
953 Y => 'y',
954 Z => 'z',
955 Key0 => '0',
956 Key1 => '1',
957 Key2 => '2',
958 Key3 => '3',
959 Key4 => '4',
960 Key5 => '5',
961 Key6 => '6',
962 Key7 => '7',
963 Key8 => '8',
964 Key9 => '9',
965 Space => ' ',
966 Minus => '-',
967 Equal => '=',
968 Period => '.',
969 Comma => ',',
970 Slash => '/',
971 Backslash => '\\',
972 Semicolon => ';',
973 Apostrophe => '\'',
974 LeftBracket => '[',
975 RightBracket => ']',
976 Backquote => '`',
977 _ => return None,
978 };
979 if !shift {
980 return Some(base);
981 }
982 Some(match base {
983 'a'..='z' => base.to_ascii_uppercase(),
984 '0' => ')',
985 '1' => '!',
986 '2' => '@',
987 '3' => '#',
988 '4' => '$',
989 '5' => '%',
990 '6' => '^',
991 '7' => '&',
992 '8' => '*',
993 '9' => '(',
994 '-' => '_',
995 '=' => '+',
996 '.' => '>',
997 ',' => '<',
998 '/' => '?',
999 '\\' => '|',
1000 ';' => ':',
1001 '\'' => '"',
1002 '[' => '{',
1003 ']' => '}',
1004 '`' => '~',
1005 other => other,
1006 })
1007}
1008
1009#[cfg(all(not(target_arch = "wasm32"), windows))]
1013fn vk_char(vk: i32, shift: bool) -> Option<char> {
1014 let base = match vk {
1015 0x41..=0x5A => (b'a' + (vk - 0x41) as u8) as char, 0x30..=0x39 => (b'0' + (vk - 0x30) as u8) as char, 0x20 => ' ', 0xBD => '-', 0xBB => '=', 0xBE => '.', 0xBC => ',', 0xBF => '/', 0xDC => '\\', 0xBA => ';', 0xDE => '\'', 0xDB => '[', 0xDD => ']', 0xC0 => '`', _ => return None,
1030 };
1031 if !shift {
1032 return Some(base);
1033 }
1034 Some(match base {
1035 'a'..='z' => base.to_ascii_uppercase(),
1036 '0' => ')',
1037 '1' => '!',
1038 '2' => '@',
1039 '3' => '#',
1040 '4' => '$',
1041 '5' => '%',
1042 '6' => '^',
1043 '7' => '&',
1044 '8' => '*',
1045 '9' => '(',
1046 '-' => '_',
1047 '=' => '+',
1048 '.' => '>',
1049 ',' => '<',
1050 '/' => '?',
1051 '\\' => '|',
1052 ';' => ':',
1053 '\'' => '"',
1054 '[' => '{',
1055 ']' => '}',
1056 '`' => '~',
1057 other => other,
1058 })
1059}
1060
1061#[cfg(all(not(target_arch = "wasm32"), windows))]
1064const TEXT_POLL_VKS: &[i32] = &[
1065 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
1066 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x30, 0x31, 0x32, 0x33,
1067 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x20, 0xBD, 0xBB, 0xBE, 0xBC, 0xBF, 0xDC, 0xBA, 0xDE,
1068 0xDB, 0xDD, 0xC0,
1069];
1070#[cfg(all(not(target_arch = "wasm32"), windows))]
1073const VK_BACK: i32 = 0x08;
1074#[cfg(all(not(target_arch = "wasm32"), windows))]
1075const VK_SHIFT: i32 = 0x10;
1076
1077#[cfg(all(not(target_arch = "wasm32"), windows))]
1084fn os_key_down(vk: i32) -> bool {
1085 unsafe {
1086 extern "system" {
1087 fn GetAsyncKeyState(vkey: i32) -> i16;
1088 }
1089 (GetAsyncKeyState(vk) as u16 & 0x8000) != 0
1090 }
1091}
1092
1093#[cfg(all(not(target_arch = "wasm32"), windows))]
1101fn window_is_foreground(hwnd: isize) -> bool {
1102 unsafe {
1103 extern "system" {
1104 fn GetForegroundWindow() -> isize;
1105 }
1106 hwnd != 0 && GetForegroundWindow() == hwnd
1107 }
1108}
1109
1110#[cfg(all(not(target_arch = "wasm32"), windows))]
1113const KEY_REPEAT_DELAY: f64 = 0.45;
1114#[cfg(all(not(target_arch = "wasm32"), windows))]
1116const KEY_REPEAT_RATE: f64 = 0.045;
1117
1118#[cfg(all(not(target_arch = "wasm32"), windows))]
1125fn key_repeat_fire(now: f64, down: bool, was_down: bool, down_since: &mut f64, last_fire: &mut f64) -> bool {
1126 if !down {
1127 return false;
1128 }
1129 if !was_down {
1130 *down_since = now;
1131 *last_fire = now;
1132 return true;
1133 }
1134 if now - *down_since >= KEY_REPEAT_DELAY && now - *last_fire >= KEY_REPEAT_RATE {
1135 *last_fire = now;
1136 return true;
1137 }
1138 false
1139}
1140
1141#[cfg(all(not(target_arch = "wasm32"), windows))]
1145fn str_to_vk(name: &str) -> Option<i32> {
1146 Some(match name {
1147 "numpad0" | "kp0" => 0x60,
1148 "numpad1" | "kp1" => 0x61,
1149 "numpad2" | "kp2" => 0x62,
1150 "numpad3" | "kp3" => 0x63,
1151 "numpad4" | "kp4" => 0x64,
1152 "numpad5" | "kp5" => 0x65,
1153 "numpad6" | "kp6" => 0x66,
1154 "numpad7" | "kp7" => 0x67,
1155 "numpad8" | "kp8" => 0x68,
1156 "numpad9" | "kp9" => 0x69,
1157 "numpad+" | "kp+" => 0x6B,
1158 "numpad-" | "kp-" => 0x6D,
1159 "numpad*" | "kp*" => 0x6A,
1160 "numpad/" | "kp/" => 0x6F,
1161 "left" => 0x25,
1162 "up" => 0x26,
1163 "right" => 0x27,
1164 "down" => 0x28,
1165 "space" => 0x20,
1166 "enter" => 0x0D,
1167 "escape" => 0x1B,
1168 "pageup" => 0x21,
1169 "pagedown" => 0x22,
1170 "lshift" | "leftshift" => 0xA0,
1171 "rshift" | "rightshift" => 0xA1,
1172 "lctrl" | "leftctrl" => 0xA2,
1173 "rctrl" | "rightctrl" => 0xA3,
1174 "lalt" | "leftalt" => 0xA4,
1175 "ralt" | "rightalt" => 0xA5,
1176 "tab" => 0x09,
1177 "backspace" => VK_BACK,
1178 "delete" => 0x2E,
1179 "insert" => 0x2D,
1180 "home" => 0x24,
1181 "end" => 0x23,
1182 "a" => 0x41,
1183 "b" => 0x42,
1184 "c" => 0x43,
1185 "d" => 0x44,
1186 "e" => 0x45,
1187 "f" => 0x46,
1188 "g" => 0x47,
1189 "h" => 0x48,
1190 "i" => 0x49,
1191 "j" => 0x4A,
1192 "k" => 0x4B,
1193 "l" => 0x4C,
1194 "m" => 0x4D,
1195 "n" => 0x4E,
1196 "o" => 0x4F,
1197 "p" => 0x50,
1198 "q" => 0x51,
1199 "r" => 0x52,
1200 "s" => 0x53,
1201 "t" => 0x54,
1202 "u" => 0x55,
1203 "v" => 0x56,
1204 "w" => 0x57,
1205 "x" => 0x58,
1206 "y" => 0x59,
1207 "z" => 0x5A,
1208 "0" => 0x30,
1209 "1" => 0x31,
1210 "2" => 0x32,
1211 "3" => 0x33,
1212 "4" => 0x34,
1213 "5" => 0x35,
1214 "6" => 0x36,
1215 "7" => 0x37,
1216 "8" => 0x38,
1217 "9" => 0x39,
1218 _ => return None,
1219 })
1220}
1221
1222fn hex_encode(bytes: &[u8]) -> String {
1224 let mut s = String::with_capacity(bytes.len() * 2);
1225 for b in bytes {
1226 s.push_str(&format!("{b:02x}"));
1227 }
1228 s
1229}
1230
1231#[cfg(not(target_arch = "wasm32"))]
1233fn decode_blob(s: &str) -> Result<Vec<u8>, String> {
1234 use base64::Engine as _;
1235 use std::io::Read as _;
1236 let comp = base64::engine::general_purpose::STANDARD
1237 .decode(s.trim())
1238 .map_err(|e| format!("base64: {e}"))?;
1239 let mut out = Vec::new();
1240 flate2::read::ZlibDecoder::new(&comp[..])
1241 .read_to_end(&mut out)
1242 .map_err(|e| format!("inflate: {e}"))?;
1243 Ok(out)
1244}
1245
1246#[cfg(not(target_arch = "wasm32"))]
1248fn hex_decode(s: &str) -> Vec<u8> {
1249 let s = s.trim();
1250 (0..s.len() / 2)
1251 .filter_map(|i| u8::from_str_radix(s.get(i * 2..i * 2 + 2)?, 16).ok())
1252 .collect()
1253}
1254
1255#[cfg(not(target_arch = "wasm32"))]
1257fn hex_to_32(s: &str) -> [u8; 32] {
1258 let v = hex_decode(s);
1259 let mut out = [0u8; 32];
1260 let n = v.len().min(32);
1261 out[..n].copy_from_slice(&v[..n]);
1262 out
1263}
1264
1265#[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1273fn build_pdf_from_images(paths: &[String], out_path: &str) -> Result<(), Box<dyn std::error::Error>> {
1274 use std::io::Write as _;
1275
1276 struct PageImg {
1277 w: u32,
1278 h: u32,
1279 compressed: Vec<u8>,
1280 }
1281
1282 let mut pages = Vec::with_capacity(paths.len());
1283 for p in paths {
1284 let img = image::open(p)?.to_rgb8();
1285 let (w, h) = (img.width(), img.height());
1286 let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
1287 enc.write_all(img.as_raw())?;
1288 pages.push(PageImg { w, h, compressed: enc.finish()? });
1289 }
1290
1291 let n = pages.len();
1292 let page_nums: Vec<u32> = (0..n).map(|i| 3 + (i as u32) * 3).collect();
1295 let total_objs = 2 + n * 3;
1296 let mut off = vec![0usize; total_objs + 1]; let mut buf: Vec<u8> = Vec::new();
1299 buf.extend_from_slice(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
1300
1301 off[1] = buf.len();
1302 buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1303
1304 off[2] = buf.len();
1305 let kids = page_nums.iter().map(|n| format!("{n} 0 R")).collect::<Vec<_>>().join(" ");
1306 buf.extend_from_slice(format!("2 0 obj\n<< /Type /Pages /Kids [{kids}] /Count {n} >>\nendobj\n").as_bytes());
1307
1308 for (i, page) in pages.iter().enumerate() {
1309 let page_num = page_nums[i];
1310 let content_num = page_num + 1;
1311 let image_num = page_num + 2;
1312 let (w, h) = (page.w, page.h);
1313
1314 off[page_num as usize] = buf.len();
1315 buf.extend_from_slice(format!(
1316 "{page_num} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {w} {h}] /Resources << /XObject << /Im0 {image_num} 0 R >> >> /Contents {content_num} 0 R >>\nendobj\n"
1317 ).as_bytes());
1318
1319 let content = format!("q {w} 0 0 {h} 0 0 cm /Im0 Do Q");
1320 off[content_num as usize] = buf.len();
1321 buf.extend_from_slice(format!(
1322 "{content_num} 0 obj\n<< /Length {} >>\nstream\n{content}\nendstream\nendobj\n",
1323 content.len()
1324 ).as_bytes());
1325
1326 off[image_num as usize] = buf.len();
1327 buf.extend_from_slice(format!(
1328 "{image_num} 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>\nstream\n",
1329 page.compressed.len()
1330 ).as_bytes());
1331 buf.extend_from_slice(&page.compressed);
1332 buf.extend_from_slice(b"\nendstream\nendobj\n");
1333 }
1334
1335 let xref_offset = buf.len();
1336 buf.extend_from_slice(format!("xref\n0 {}\n", total_objs + 1).as_bytes());
1337 buf.extend_from_slice(b"0000000000 65535 f \n");
1338 for entry in off.iter().skip(1) {
1339 buf.extend_from_slice(format!("{entry:010} 00000 n \n").as_bytes());
1340 }
1341 buf.extend_from_slice(
1342 format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF", total_objs + 1).as_bytes(),
1343 );
1344
1345 std::fs::write(out_path, buf)?;
1346 Ok(())
1347}
1348
1349fn tex_rgb(r: f32, g: f32, b: f32) -> u32 {
1350 ((r * 255.0) as u32) << 16 | ((g * 255.0) as u32) << 8 | (b * 255.0) as u32
1351}
1352
1353const PERM: [u8; 512] = [
1356 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1357 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1358 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1359 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1360 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1361 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1362 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1363 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1364 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1365 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1366 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180,
1367 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
1368 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
1369 140, 161, 137, 13, 191, 230, 66, 104, 153, 199, 167, 147, 99, 179, 92,
1370 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
1372 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
1373 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 35, 63, 189, 114, 56, 42, 123,
1374 165, 38, 72, 93, 69, 139, 138, 78, 149, 159, 56, 89, 152, 78, 61, 140, 63, 26, 142, 76, 124,
1375 132, 72, 11, 90, 44, 82, 59, 96, 41, 148, 126, 157, 13, 49, 27, 176, 33, 47, 14, 97, 78, 71,
1376 40, 87, 183, 4, 122, 92, 7, 72, 3, 246, 17, 225, 87, 91, 106, 203, 190, 57, 74, 76, 88, 207,
1377 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146,
1378 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196,
1379 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94,
1380 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55,
1381 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174,
1382];
1383
1384fn fade(t: f32) -> f32 {
1385 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
1386}
1387
1388fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
1389 let h = hash & 15;
1390 let u = if h < 8 { x } else { y };
1391 let v = if h < 8 { y } else { z };
1392 (if (h & 1) == 0 { u } else { -u }) + (if (h & 2) == 0 { v } else { -v })
1393}
1394
1395fn perlin3(x: f32, y: f32, z: f32) -> f32 {
1396 let xi = (x.floor() as i32) & 255;
1397 let yi = (y.floor() as i32) & 255;
1398 let zi = (z.floor() as i32) & 255;
1399
1400 let xf = x - x.floor();
1401 let yf = y - y.floor();
1402 let zf = z - z.floor();
1403
1404 let u = fade(xf);
1405 let v = fade(yf);
1406 let w = fade(zf);
1407
1408 let p0 = PERM[xi as usize] as usize;
1409 let p1 = PERM[((xi + 1) & 255) as usize] as usize;
1410 let pa = PERM[(p0 + yi as usize) & 255] as usize;
1411 let pb = PERM[(p0 + ((yi + 1) & 255) as usize) & 255] as usize;
1412 let pc = PERM[(p1 + yi as usize) & 255] as usize;
1413 let pd = PERM[(p1 + ((yi + 1) & 255) as usize) & 255] as usize;
1414
1415 let g000 = grad(PERM[(pa + zi as usize) & 255], xf, yf, zf);
1416 let g001 = grad(
1417 PERM[(pa + ((zi + 1) & 255) as usize) & 255],
1418 xf,
1419 yf,
1420 zf - 1.0,
1421 );
1422 let g010 = grad(PERM[(pb + zi as usize) & 255], xf, yf - 1.0, zf);
1423 let g011 = grad(
1424 PERM[(pb + ((zi + 1) & 255) as usize) & 255],
1425 xf,
1426 yf - 1.0,
1427 zf - 1.0,
1428 );
1429 let g100 = grad(PERM[(pc + zi as usize) & 255], xf - 1.0, yf, zf);
1430 let g101 = grad(
1431 PERM[(pc + ((zi + 1) & 255) as usize) & 255],
1432 xf - 1.0,
1433 yf,
1434 zf - 1.0,
1435 );
1436 let g110 = grad(PERM[(pd + zi as usize) & 255], xf - 1.0, yf - 1.0, zf);
1437 let g111 = grad(
1438 PERM[(pd + ((zi + 1) & 255) as usize) & 255],
1439 xf - 1.0,
1440 yf - 1.0,
1441 zf - 1.0,
1442 );
1443
1444 let l00 = g000 + u * (g100 - g000);
1445 let l01 = g001 + u * (g101 - g001);
1446 let l10 = g010 + u * (g110 - g010);
1447 let l11 = g011 + u * (g111 - g011);
1448
1449 let l0 = l00 + v * (l10 - l00);
1450 let l1 = l01 + v * (l11 - l01);
1451
1452 l0 + w * (l1 - l0)
1453}
1454
1455fn fast_rand_f64(state: &mut u64) -> f64 {
1456 *state = state
1457 .wrapping_mul(6364136223846793005)
1458 .wrapping_add(1442695040888963407);
1459 ((*state >> 32) as u32) as f64 / 4294967296.0
1460}
1461
1462#[inline]
1466fn put_px(buf: &mut [u32], idx: usize, color: u32, blend: u8) {
1467 if idx >= buf.len() {
1468 return;
1469 }
1470 if blend == 0 {
1471 buf[idx] = color;
1472 } else {
1473 let old = buf[idx];
1474 let r = (((old >> 16) & 255) + ((color >> 16) & 255)).min(255);
1475 let g = (((old >> 8) & 255) + ((color >> 8) & 255)).min(255);
1476 let b = ((old & 255) + (color & 255)).min(255);
1477 buf[idx] = (r << 16) | (g << 8) | b;
1478 }
1479}
1480
1481#[inline]
1483fn rgb(r: f64, g: f64, b: f64) -> u32 {
1484 let r = (r as i64).clamp(0, 255) as u32;
1485 let g = (g as i64).clamp(0, 255) as u32;
1486 let b = (b as i64).clamp(0, 255) as u32;
1487 (r << 16) | (g << 8) | b
1488}
1489
1490#[allow(clippy::too_many_arguments)]
1491fn draw_circle_outline(
1492 buf: &mut [u32],
1493 w: i32,
1494 h: i32,
1495 cx: i32,
1496 cy: i32,
1497 r: i32,
1498 color: u32,
1499 blend: u8,
1500) {
1501 let r = r.clamp(0, 20000); if r == 0 {
1503 return;
1504 }
1505 let mut x = 0;
1506 let mut y = r;
1507 let mut d = 3 - 2 * r;
1508 while x <= y {
1509 plot_circle_points(buf, w, h, cx, cy, x, y, color, blend);
1510 if d < 0 {
1511 d += 4 * x + 6;
1512 } else {
1513 d += 4 * (x - y) + 10;
1514 y -= 1;
1515 }
1516 x += 1;
1517 }
1518}
1519
1520#[allow(clippy::too_many_arguments)]
1521fn plot_circle_points(
1522 buf: &mut [u32],
1523 w: i32,
1524 h: i32,
1525 cx: i32,
1526 cy: i32,
1527 x: i32,
1528 y: i32,
1529 color: u32,
1530 blend: u8,
1531) {
1532 let points = [
1533 (cx + x, cy + y),
1534 (cx - x, cy + y),
1535 (cx + x, cy - y),
1536 (cx - x, cy - y),
1537 (cx + y, cy + x),
1538 (cx - y, cy + x),
1539 (cx + y, cy - x),
1540 (cx - y, cy - x),
1541 ];
1542 for &(px, py) in &points {
1543 if px >= 0 && px < w && py >= 0 && py < h {
1544 put_px(buf, (py * w + px) as usize, color, blend);
1545 }
1546 }
1547}
1548
1549#[allow(clippy::too_many_arguments)]
1550fn draw_circle_filled(
1551 buf: &mut [u32],
1552 w: i32,
1553 h: i32,
1554 cx: i32,
1555 cy: i32,
1556 r: i32,
1557 color: u32,
1558 blend: u8,
1559) {
1560 if r <= 0 {
1561 return;
1562 }
1563 for dy in -r..=r {
1564 let dx_max = ((r * r - dy * dy) as f64).sqrt() as i32;
1565 let py = cy + dy;
1566 if py < 0 || py >= h {
1567 continue;
1568 }
1569 for dx in -dx_max..=dx_max {
1570 let px = cx + dx;
1571 if px >= 0 && px < w {
1572 put_px(buf, (py * w + px) as usize, color, blend);
1573 }
1574 }
1575 }
1576}
1577
1578#[cfg(test)]
1579mod draw_tests {
1580 use super::*;
1581
1582 #[test]
1583 fn filled_circle_actually_writes_pixels() {
1584 let mut buf = vec![0u32; 100 * 100];
1585 draw_circle_filled(&mut buf, 100, 100, 50, 50, 10, 0xFF00FF, 0);
1586 assert_eq!(buf[50 * 100 + 50], 0xFF00FF, "centre pixel must be filled");
1587 assert_eq!(buf[0], 0, "far corner must stay clear");
1588 let n = buf.iter().filter(|&&p| p != 0).count();
1589 assert!(n > 200 && n < 500, "r=10 disc area ≈ 314, got {n}");
1590 }
1591
1592 #[test]
1593 fn circle_outline_writes_a_ring() {
1594 let mut buf = vec![0u32; 100 * 100];
1595 draw_circle_outline(&mut buf, 100, 100, 50, 50, 20, 0x00FF00, 0);
1596 assert_eq!(buf[50 * 100 + 50], 0, "outline must NOT fill the centre");
1597 assert!(
1598 buf.iter().any(|&p| p == 0x00FF00),
1599 "outline must draw a ring"
1600 );
1601 }
1602
1603 #[test]
1604 fn additive_blend_accumulates_channels() {
1605 let mut buf = vec![0x202020u32; 1];
1606 put_px(&mut buf, 0, 0x404040, 1);
1607 assert_eq!(buf[0], 0x606060);
1608 }
1609}
1610
1611#[derive(Clone, Copy)]
1617pub struct UiTheme {
1618 pub primary: u32,
1619 pub accent: u32,
1620 pub track: u32,
1621 pub warn: u32,
1622 pub text: u32,
1623 pub bg: u32,
1624}
1625
1626impl Default for UiTheme {
1627 fn default() -> Self {
1628 Self {
1629 primary: 0x00D2FF, accent: 0x28FFB4, track: 0x2C3E64, warn: 0xFF5A5A, text: 0xBEEBFF, bg: 0x0A1018, }
1636 }
1637}
1638
1639#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
1640pub struct Interpreter {
1641 globals: HashMap<String, Expr>,
1642 global_seed: Env,
1645 functions: FxHashMap<String, Rc<FnDef>>,
1646 pub(crate) structs: HashMap<String, Vec<String>>,
1648 enum_variants: HashMap<String, (String, usize)>,
1650 _modules: HashMap<String, Vec<FnDef>>,
1651 gfx: RefCell<GfxState>,
1652 svg: RefCell<Option<SvgWriter>>,
1653 pub source_dir: Option<std::path::PathBuf>,
1655 loaded_files: std::collections::HashSet<String>,
1657 #[cfg(not(target_arch = "wasm32"))]
1659 audio: Option<AudioEngine>,
1660 #[cfg(not(target_arch = "wasm32"))]
1661 fft: RefCell<FftAnalyzer>,
1662 fft_bands_cache: RefCell<Vec<f32>>,
1663 start_time_secs: f64,
1666 frame_num: u64,
1668 #[cfg(target_arch = "wasm32")]
1670 wasm_target_fps: f64,
1671 #[cfg(target_arch = "wasm32")]
1673 wasm_next_present_ms: f64,
1674 rand_state: u64,
1676 #[cfg(not(target_arch = "wasm32"))]
1678 mic: Option<ling_mic::MicInput>,
1679 #[cfg(not(target_arch = "wasm32"))]
1681 crypto_ids: Vec<ling_crypto::KnotIdentity>,
1682 #[cfg(not(target_arch = "wasm32"))]
1684 ed25519_ids: Vec<ling_crypto::Ed25519Keypair>,
1685 text_buffer: String,
1687 record_n: u32,
1689 #[cfg(not(target_arch = "wasm32"))]
1691 mic_buffer: Vec<f32>,
1692 #[cfg(not(target_arch = "wasm32"))]
1694 fonts: Vec<ling_graphics::VectorFont>,
1695 images: Vec<image::RgbaImage>,
1698 ui_theme: UiTheme,
1700 mouse_was_down: bool,
1702 #[cfg(not(target_arch = "wasm32"))]
1704 music: Option<ling_music::MusicEngine>,
1705 #[cfg(not(target_arch = "wasm32"))]
1706 music_init: bool,
1707 tracks: Vec<ling_music::DecodedAudio>,
1709 lyrics: Vec<ling_music::Lyrics>,
1711 midis: Vec<ling_music::MidiSong>,
1713 soft_bodies: Vec<ling_physics::soft::SoftBody>,
1715 rigid_world: ling_physics::rigid::PhysicsWorld,
1717 liquids: Vec<ling_physics::liquid::LiquidGrid>,
1719 meshes: Vec<crate::gfx::shapes::ColorMesh>,
1720 gltf_models: std::cell::RefCell<Vec<ling_physics::gltf::GltfModel>>,
1722 dialog: Option<ling_game::dialog::Dialog>,
1724 dialog_colors: [u32; 4],
1726 frames: Vec<String>,
1728 error_trace: Option<Vec<String>>,
1731 #[cfg(not(target_arch = "wasm32"))]
1735 input: RefCell<Option<InputState>>,
1736 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1741 http_routes: Vec<(String, String, Value)>,
1742 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1746 db: Option<ling_http::rusqlite::Connection>,
1747 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1750 http_static_dirs: Vec<(String, String)>,
1751 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1755 async_jobs: web::AsyncJobs,
1756}
1757
1758#[cfg(not(target_arch = "wasm32"))]
1760struct InputState {
1761 sensorium: ling_input::Sensorium,
1762 backend: ling_input::backend::GilrsBackend,
1763}
1764
1765impl Default for Interpreter {
1766 fn default() -> Self {
1767 Self::new()
1768 }
1769}
1770
1771impl Interpreter {
1772 pub fn new() -> Self {
1773 #[cfg(not(target_arch = "wasm32"))]
1774 let audio = AudioEngine::new()
1775 .map_err(|e| eprintln!("audio init failed (no sound): {e}"))
1776 .ok();
1777 Self {
1778 globals: HashMap::new(),
1779 global_seed: new_env(),
1780 functions: FxHashMap::default(),
1781 structs: HashMap::new(),
1782 enum_variants: HashMap::new(),
1783 _modules: HashMap::new(),
1784 gfx: RefCell::new(GfxState::new()),
1785 svg: RefCell::new(None),
1786 source_dir: None,
1787 loaded_files: std::collections::HashSet::new(),
1788 #[cfg(not(target_arch = "wasm32"))]
1789 audio,
1790 #[cfg(not(target_arch = "wasm32"))]
1791 fft: RefCell::new(FftAnalyzer::new(2048, 44100)),
1792 fft_bands_cache: RefCell::new(vec![]),
1793 start_time_secs: crate::runtime::now_secs(),
1794 frame_num: 0,
1795 #[cfg(target_arch = "wasm32")]
1796 wasm_target_fps: 60.0,
1797 #[cfg(target_arch = "wasm32")]
1798 wasm_next_present_ms: 0.0,
1799 rand_state: 0x123456789ABCDEF,
1800 #[cfg(not(target_arch = "wasm32"))]
1801 mic: None,
1802 #[cfg(not(target_arch = "wasm32"))]
1803 crypto_ids: Vec::new(),
1804 #[cfg(not(target_arch = "wasm32"))]
1805 ed25519_ids: Vec::new(),
1806 text_buffer: String::new(),
1807 record_n: 0,
1808 #[cfg(not(target_arch = "wasm32"))]
1809 mic_buffer: Vec::new(),
1810 #[cfg(not(target_arch = "wasm32"))]
1811 fonts: Vec::new(),
1812 images: Vec::new(),
1813 ui_theme: UiTheme::default(),
1814 mouse_was_down: false,
1815 #[cfg(not(target_arch = "wasm32"))]
1816 music: None,
1817 #[cfg(not(target_arch = "wasm32"))]
1818 music_init: false,
1819 tracks: Vec::new(),
1820 lyrics: Vec::new(),
1821 midis: Vec::new(),
1822 soft_bodies: Vec::new(),
1823 rigid_world: ling_physics::rigid::PhysicsWorld::new(),
1824 liquids: Vec::new(),
1825 meshes: Vec::new(),
1826 gltf_models: std::cell::RefCell::new(Vec::new()),
1827 dialog: None,
1828 dialog_colors: [0xE6F2FF, 0xFFD24A, 0x4AD2FF, 0x6CFF8C], frames: Vec::new(),
1830 error_trace: None,
1831 #[cfg(not(target_arch = "wasm32"))]
1832 input: RefCell::new(None),
1833 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1834 http_routes: Vec::new(),
1835 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1836 db: None,
1837 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1838 http_static_dirs: Vec::new(),
1839 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
1840 async_jobs: web::AsyncJobs::new(),
1841 }
1842 }
1843
1844 #[cfg(not(target_arch = "wasm32"))]
1848 fn pad_poll(&self) -> usize {
1849 let mut slot = self.input.borrow_mut();
1850 if slot.is_none() {
1851 match ling_input::backend::GilrsBackend::new() {
1852 Ok(backend) => {
1853 *slot = Some(InputState { sensorium: ling_input::Sensorium::new(4), backend });
1854 },
1855 Err(_) => return 0,
1856 }
1857 }
1858 let st = slot.as_mut().unwrap();
1859 st.sensorium.begin_frame();
1860 st.sensorium.pump(&mut st.backend);
1861 st.sensorium.update(1.0 / 60.0);
1862 st.sensorium.devices.count()
1863 }
1864
1865 #[cfg(not(target_arch = "wasm32"))]
1868 fn with_pad<T>(&self, slot: usize, default: T, f: impl FnOnce(&ling_input::Gamepad) -> T) -> T {
1869 let inp = self.input.borrow();
1870 match inp.as_ref().and_then(|s| s.sensorium.player(slot)) {
1871 Some(p) => f(p),
1872 None => default,
1873 }
1874 }
1875
1876 pub fn take_error_trace(&mut self) -> Vec<String> {
1879 self.error_trace.take().unwrap_or_default()
1880 }
1881
1882 #[cfg(target_arch = "wasm32")]
1883 fn wasm_pace_frame(&mut self) {
1884 let fps = self.wasm_target_fps.max(1.0);
1885 let frame_ms = 1000.0 / fps;
1886 let now = js_sys::Date::now();
1887 if self.wasm_next_present_ms <= 0.0 {
1888 self.wasm_next_present_ms = now + frame_ms;
1889 return;
1890 }
1891
1892 let wait_ms = (self.wasm_next_present_ms - now).floor() as i32;
1893 if wait_ms > 0 {
1894 wasm_sleep_ms(wait_ms);
1895 }
1896
1897 let after = js_sys::Date::now();
1898 if after > self.wasm_next_present_ms + frame_ms * 3.0 {
1899 self.wasm_next_present_ms = after + frame_ms;
1900 } else {
1901 self.wasm_next_present_ms += frame_ms;
1902 }
1903 }
1904
1905 fn framed<T, F>(&mut self, name: &str, body: F) -> Result<T, EvalErr>
1908 where
1909 F: FnOnce(&mut Self) -> Result<T, EvalErr>,
1910 {
1911 self.frames.push(name.to_string());
1912 let result = body(self);
1913 if matches!(result, Err(EvalErr::Runtime(_))) && self.error_trace.is_none() {
1914 self.error_trace = Some(self.frames.clone());
1915 }
1916 self.frames.pop();
1917 result
1918 }
1919
1920 #[cfg(not(target_arch = "wasm32"))]
1924 fn render_dialog(&mut self, x: f32, y: f32, w: f32, h: f32, font: i64, t: f32) {
1925 let (runs, typing) = match &self.dialog {
1926 Some(d) if !d.is_closed() => {
1927 let runs: Vec<(String, usize, bool)> = d
1928 .visible_runs()
1929 .into_iter()
1930 .map(|r| (r.text, r.role.index(), r.newline_before))
1931 .collect();
1932 (runs, d.is_typing())
1933 },
1934 _ => return,
1935 };
1936 let colors = self.dialog_colors;
1937 let b = 12.0;
1939 let corners: Vec<[f32; 2]> = vec![
1940 [x + b, y],
1941 [x + w - b, y],
1942 [x + w, y + b],
1943 [x + w, y + h - b],
1944 [x + w - b, y + h],
1945 [x + b, y + h],
1946 [x, y + h - b],
1947 [x, y + b],
1948 [x + b, y],
1949 ];
1950 {
1951 let mut gfx = self.gfx.borrow_mut();
1952 let (bw, bh) = (gfx.width, gfx.height);
1953 crate::gfx::raster::fill_contours_aa(
1954 &mut gfx.buffer,
1955 bw,
1956 bh,
1957 0x0A1018,
1958 false,
1959 std::slice::from_ref(&corners),
1960 );
1961 for seg in corners.windows(2) {
1962 crate::gfx::raster::draw_line_aa(
1963 &mut gfx.buffer,
1964 bw,
1965 bh,
1966 0x00D2FF,
1967 false,
1968 seg[0][0],
1969 seg[0][1],
1970 seg[1][0],
1971 seg[1][1],
1972 );
1973 }
1974 }
1975 let px = 22.0f32;
1977 let pad = 20.0f32;
1978 let line_h = px * 1.45;
1979 let mut cx = x + pad;
1980 let mut cy = y + pad;
1981 let use_font = font >= 0 && (font as usize) < self.fonts.len();
1982 for (text, role, nl) in &runs {
1983 if *nl {
1984 cx = x + pad;
1985 cy += line_h;
1986 }
1987 for word in text.split_inclusive(' ') {
1988 let wpx = if use_font {
1989 self.fonts[font as usize].measure(word, px)
1990 } else {
1991 ling_ui::holo::text_width(word, px * 0.6, px * 0.24)
1992 };
1993 if cx + wpx > x + w - pad && cx > x + pad + 1.0 {
1994 cx = x + pad;
1995 cy += line_h;
1996 }
1997 if cy + line_h > y + h {
1998 break;
1999 }
2000 let col = colors[(*role).min(3)];
2001 if use_font {
2002 let glyphs = self.font_layout_2d_glyphs(font as usize, cx, cy, px, word);
2003 let mut gfx = self.gfx.borrow_mut();
2004 let (bw, bh, add) = (gfx.width, gfx.height, gfx.blend == 1);
2005 for contours in &glyphs {
2006 crate::gfx::raster::fill_contours_aa(
2007 &mut gfx.buffer,
2008 bw,
2009 bh,
2010 col,
2011 add,
2012 contours,
2013 );
2014 }
2015 } else {
2016 let segs = ling_ui::holo::text_lines(word, cx, cy, px * 0.6, px, px * 0.24);
2017 let mut gfx = self.gfx.borrow_mut();
2018 let (bw, bh) = (gfx.width, gfx.height);
2019 for s in segs {
2020 draw_line(&mut gfx.buffer, bw, bh, col, s[0], s[1], s[2], s[3]);
2021 }
2022 }
2023 cx += wpx;
2024 }
2025 }
2026 if !typing && (t * 3.0).sin() > 0.0 {
2028 let ax = x + w - 26.0;
2029 let ay = y + h - 22.0;
2030 let mut gfx = self.gfx.borrow_mut();
2031 let (bw, bh) = (gfx.width, gfx.height);
2032 crate::gfx::raster::fill_contours_aa(
2033 &mut gfx.buffer,
2034 bw,
2035 bh,
2036 0x00D2FF,
2037 false,
2038 std::slice::from_ref(&vec![
2039 [ax - 7.0, ay],
2040 [ax + 7.0, ay],
2041 [ax, ay + 9.0],
2042 [ax - 7.0, ay],
2043 ]),
2044 );
2045 }
2046 }
2047
2048 #[cfg(not(target_arch = "wasm32"))]
2051 fn ensure_music(&mut self) -> bool {
2052 if self.music.is_some() {
2053 return true;
2054 }
2055 if self.music_init {
2056 return false;
2057 }
2058 self.music_init = true;
2059 match ling_music::MusicEngine::new() {
2060 Ok(m) => {
2061 self.music = Some(m);
2062 true
2063 },
2064 Err(e) => {
2065 eprintln!("music engine init failed (no music playback): {e}");
2066 false
2067 },
2068 }
2069 }
2070
2071 #[cfg(target_arch = "wasm32")]
2072 fn wasm_resolve_source_path(&self, path: &str) -> String {
2073 let p = path.trim();
2074 if p.is_empty() {
2075 return String::new();
2076 }
2077 if p.contains("://") || p.starts_with('/') || p.starts_with("./") || p.starts_with("../") {
2078 return p.to_string();
2079 }
2080 if let Some(d) = &self.source_dir {
2081 let base = d.to_string_lossy().replace('\\', "/");
2082 if !base.is_empty() {
2083 return format!(
2084 "{}/{}",
2085 base.trim_end_matches('/'),
2086 p.trim_start_matches("./")
2087 );
2088 }
2089 }
2090 p.to_string()
2091 }
2092
2093 #[cfg(target_arch = "wasm32")]
2094 fn wasm_music_builtin(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, EvalErr> {
2095 match name {
2096 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" | "charger_musique" | "musik_laden" | "загрузить_музыку" =>
2098 {
2099 let path = self.arg_str(args, 0, "");
2100 let resolved = self.wasm_resolve_source_path(&path);
2101 match wasm_fetch_bytes(&resolved)
2102 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
2103 {
2104 Ok(t) => {
2105 let id = self.tracks.len();
2106 self.tracks.push(t);
2107 return Ok(Some(Value::Number(id as f64)));
2108 },
2109 Err(e) => {
2110 eprintln!("music_load failed ({path}): {e}");
2111 return Ok(Some(Value::Number(-1.0)));
2112 },
2113 }
2114 },
2115 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" | "durée_musique" | "musik_dauer" | "длительность_музыки" =>
2116 {
2117 let id = self.arg_num(args, 0, 0.0)? as i64;
2118 let d = self
2119 .tracks
2120 .get(id as usize)
2121 .map(|t| t.duration)
2122 .unwrap_or(0.0);
2123 return Ok(Some(Value::Number(d as f64)));
2124 },
2125 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" | "ضربان_در_دقیقه" | "نبضات_بالدقيقة" | "פעימות_לדקה" | "بی_پی_ایم" | "bpm_musique" | "musik_bpm" | "музыка_bpm" =>
2126 {
2127 let id = self.arg_num(args, 0, 0.0)? as i64;
2128 let b = self
2129 .tracks
2130 .get(id as usize)
2131 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
2132 .unwrap_or(0.0);
2133 return Ok(Some(Value::Number(b as f64)));
2134 },
2135 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" | "tonalité_musique" | "musik_tonart" | "тональность_музыки" => {
2136 let id = self.arg_num(args, 0, 0.0)? as i64;
2137 let k = self
2138 .tracks
2139 .get(id as usize)
2140 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
2141 .unwrap_or_default();
2142 return Ok(Some(Value::Str(k)));
2143 },
2144 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" | "attaques_musique" | "musik_einsätze" | "атаки_музыки" =>
2145 {
2146 let id = self.arg_num(args, 0, 0.0)? as i64;
2147 let v = self
2148 .tracks
2149 .get(id as usize)
2150 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
2151 .unwrap_or_default();
2152 return Ok(Some(Value::List(
2153 v.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2154 )));
2155 },
2156 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" | "شبکه_ضرب" | "شبكة_الإيقاع" | "רשת_פעימות" | "بیٹ_گرڈ" | "grille_temps_musique" | "musik_taktraster" | "сетка_ритма_музыки" =>
2157 {
2158 let id = self.arg_num(args, 0, 0.0)? as i64;
2159 let beats = self
2160 .tracks
2161 .get(id as usize)
2162 .map(|t| {
2163 let b = ling_music::analysis::bpm(&t.mono, t.rate);
2164 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
2165 })
2166 .unwrap_or_default();
2167 return Ok(Some(Value::List(
2168 beats.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2169 )));
2170 },
2171 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" | "بارگذاری_متن_ترانه" | "تحميل_كلمات_الأغنية" | "טעינת_מילות_שיר" | "گیت_متن_لوڈ" | "lrc_musique" | "musik_lrc" | "lrc_музыки" =>
2172 {
2173 let path = self.arg_str(args, 0, "");
2174 let resolved = self.wasm_resolve_source_path(&path);
2175 match wasm_fetch_text(&resolved) {
2176 Ok(text) => {
2177 let id = self.lyrics.len();
2178 self.lyrics.push(ling_music::Lyrics::parse(&text));
2179 return Ok(Some(Value::Number(id as f64)));
2180 },
2181 Err(e) => {
2182 eprintln!("music_lrc failed ({path}): {e}");
2183 return Ok(Some(Value::Number(-1.0)));
2184 },
2185 }
2186 },
2187 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" | "متن_ترانه_فعلی" | "كلمات_الأغنية_الحالية" | "מילות_שיר_נוכחיות" | "موجودہ_گیت_متن" | "paroles_musique" | "musik_liedtext" | "текст_песни" =>
2188 {
2189 let id = self.arg_num(args, 0, 0.0)? as i64;
2190 let t = self.arg_num(args, 1, 0.0)? as f32;
2191 let line = self
2192 .lyrics
2193 .get(id as usize)
2194 .map(|l| l.line_at(t).to_string())
2195 .unwrap_or_default();
2196 return Ok(Some(Value::Str(line)));
2197 },
2198 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" | "بارگذاری_MIDI" | "تحميل_MIDI" | "טעינת_MIDI" | "MIDI_لوڈ" | "charger_midi_musique" | "musik_midi_laden" | "загрузить_midi_музыки" =>
2199 {
2200 let path = self.arg_str(args, 0, "");
2201 let resolved = self.wasm_resolve_source_path(&path);
2202 match wasm_fetch_bytes(&resolved).and_then(|bytes| {
2203 ling_music::midi::from_bytes(&bytes).map_err(|e| e.to_string())
2204 }) {
2205 Ok(m) => {
2206 let id = self.midis.len();
2207 self.midis.push(m);
2208 return Ok(Some(Value::Number(id as f64)));
2209 },
2210 Err(e) => {
2211 eprintln!("music_midi_load failed ({path}): {e}");
2212 return Ok(Some(Value::Number(-1.0)));
2213 },
2214 }
2215 },
2216 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" | "تعداد_MIDI" | "عدد_MIDI" | "מספר_MIDI" | "MIDI_تعداد" | "nombre_midi_musique" | "musik_midi_anzahl" | "число_midi_музыки" =>
2217 {
2218 let id = self.arg_num(args, 0, 0.0)? as i64;
2219 let n = self
2220 .midis
2221 .get(id as usize)
2222 .map(|m| m.notes.len())
2223 .unwrap_or(0);
2224 return Ok(Some(Value::Number(n as f64)));
2225 },
2226 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" | "نتهای_MIDI" | "نغمات_MIDI" | "תווי_MIDI" | "MIDI_نوٹس" | "notes_midi_musique" | "musik_midi_noten" | "ноты_midi_музыки" =>
2227 {
2228 let id = self.arg_num(args, 0, 0.0)? as i64;
2229 let mut out = Vec::new();
2230 if let Some(m) = self.midis.get(id as usize) {
2231 for n in &m.notes {
2232 out.push(Value::Number(n.time as f64));
2233 out.push(Value::Number(n.midi as f64));
2234 }
2235 }
2236 return Ok(Some(Value::List(out.into())));
2237 },
2238 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" | "میلههای_MIDI" | "أعمدة_MIDI" | "עמודות_MIDI" | "MIDI_بارز" | "mesures_midi_musique" | "musik_midi_takte" | "такты_midi_музыки" =>
2239 {
2240 let id = self.arg_num(args, 0, 0.0)? as i64;
2241 let mut out = Vec::new();
2242 if let Some(m) = self.midis.get(id as usize) {
2243 for n in &m.notes {
2244 out.push(Value::Number(n.time as f64));
2245 out.push(Value::Number(n.midi as f64));
2246 out.push(Value::Number(n.dur as f64));
2247 }
2248 }
2249 return Ok(Some(Value::List(out.into())));
2250 },
2251 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" | "داوری_ضرب" | "حكم_الإيقاع" | "שיפוט_קצב" | "بیٹ_فیصلہ" | "juger_musique" | "musik_bewerten" | "оценить_музыку" =>
2252 {
2253 let delta_ms = self.arg_num(args, 0, 9999.0)? as f32;
2254 return Ok(Some(Value::Number(
2255 ling_music::Grade::judge(delta_ms).index() as f64,
2256 )));
2257 },
2258 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" | "نام_رتبه" | "اسم_التقييم" | "שם_דירוג" | "گریڈ_نام" | "nom_grade_musique" | "musik_bewertungsname" | "имя_оценки_музыки" =>
2259 {
2260 let idx = self.arg_num(args, 0, 4.0)? as i32;
2261 return Ok(Some(Value::Str(
2262 ling_music::Grade::from_index(idx).name().to_string(),
2263 )));
2264 },
2265 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" | "نام_نت" | "اسم_النغمة" | "שם_תו" | "نوٹ_نام" | "nom_note_musique" | "musik_notenname" | "имя_ноты_музыки" =>
2266 {
2267 let hz = self.arg_num(args, 0, 0.0)? as f32;
2268 return Ok(Some(Value::Str(ling_music::note::hz_to_name(hz))));
2269 },
2270 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" | "فرکانس_نت" | "تردد_النغمة" | "תדר_תו" | "نوٹ_ہرٹز" | "hz_musique" | "musik_hz" | "музыка_гц" =>
2271 {
2272 let midi = match args.get(0) {
2273 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(69),
2274 Some(Value::Number(n)) => *n as i32,
2275 _ => 69,
2276 };
2277 return Ok(Some(Value::Number(
2278 ling_music::note::midi_to_hz(midi as f32) as f64,
2279 )));
2280 },
2281 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" | "امتیاز_زیروبمی" | "درجة_طبقة_الصوت" | "ציון_גובה_צליל" | "پچ_اسکور" | "score_hauteur_musique" | "musik_tonhöhen_punktzahl" | "счёт_высоты_тона" =>
2282 {
2283 let hz = self.arg_num(args, 0, 0.0)? as f32;
2284 let target = self.arg_num(args, 1, 0.0)? as f32;
2285 return Ok(Some(Value::Number(
2286 ling_music::karaoke::pitch_score(hz, target) as f64,
2287 )));
2288 },
2289
2290 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" | "jouer_musique" | "musik_abspielen" | "играть_музыку" =>
2292 {
2293 let id = self.arg_num(args, 0, 0.0)? as usize;
2294 if let Some(t) = self.tracks.get(id) {
2295 crate::gfx::audio_web::play_music(id, &t.stereo, t.channels, t.rate, 1.0);
2296 }
2297 return Ok(Some(Value::Unit));
2298 },
2299 "music_pause"
2300 | "暂停音乐"
2301 | "音楽一時停止"
2302 | "음악일시정지"
2303 | "หยุดเพลงชั่วคราว"
2304 | "music_stop"
2305 | "停止音乐"
2306 | "音楽停止"
2307 | "음악정지"
2308 | "หยุดเพลง" | "مکث_موسیقی" | "ألبث_الموسيقى" | "השהה_מוזיקה" | "موسیقی_روکو_مؤقت" | "pause_musique" | "musik_pausieren" | "пауза_музыки" => {
2309 let id = self.arg_num(args, 0, 0.0)? as usize;
2310 crate::gfx::audio_web::stop_music(id);
2311 return Ok(Some(Value::Unit));
2312 },
2313 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" | "جستجوی_موسیقی" | "ابحث_في_الموسيقى" | "חפש_במוזיקה" | "موسیقی_تلاش" | "chercher_musique" | "musik_suchen" | "перемотать_музыку" =>
2314 {
2315 return Ok(Some(Value::Unit));
2317 },
2318 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" | "موقعیت_موسیقی" | "موضع_الموسيقى" | "מיקום_מוזיקה" | "موسیقی_مقام" | "position_musique" | "musik_position" | "позиция_музыки" =>
2319 {
2320 return Ok(Some(Value::Number(
2321 crate::gfx::audio_web::current_music_position(),
2322 )));
2323 },
2324 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" | "بلندی_موسیقی" | "مستوى_الموسيقى" | "עוצמת_מוזיקה" | "موسیقی_شدت" | "volume_musique" | "musik_lautstärke" | "громкость_музыки" =>
2325 {
2326 let vol = self.arg_num(args, 0, 0.8)? as f32;
2327 crate::gfx::audio_web::set_music_volume(0, vol);
2329 return Ok(Some(Value::Unit));
2330 },
2331
2332 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" | "طیف_موسیقی" | "طيف_الموسيقى" | "ספקטרום_מוזיקה" | "میوزک_اسپیکٹرم" | "fft_musique" | "musik_fft" | "fft_музыки" =>
2334 {
2335 let id = self.arg_num(args, 0, 0.0)? as usize;
2336 let nbands = self.arg_num(args, 1, 16.0)? as usize;
2337 let pos = crate::gfx::audio_web::current_music_position() as f32;
2338 let bands = if let Some(t) = self.tracks.get(id) {
2339 ling_music::analysis::fft_bands_at_pos(&t.mono, t.rate, pos, nbands)
2340 } else {
2341 vec![0.0f32; nbands]
2342 };
2343 return Ok(Some(Value::List(
2344 bands.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>().into(),
2345 )));
2346 },
2347
2348 _ => {},
2349 }
2350 Ok(None)
2351 }
2352
2353 #[cfg(not(target_arch = "wasm32"))]
2357 fn font_layout_2d(
2358 &mut self,
2359 id: usize,
2360 x: f32,
2361 y: f32,
2362 px: f32,
2363 text: &str,
2364 ) -> Vec<Vec<[f32; 2]>> {
2365 let mut out = Vec::new();
2366 for g in self.font_layout_2d_glyphs(id, x, y, px, text) {
2367 out.extend(g);
2368 }
2369 out
2370 }
2371
2372 #[cfg(not(target_arch = "wasm32"))]
2375 fn font_layout_2d_glyphs(
2376 &mut self,
2377 id: usize,
2378 x: f32,
2379 y: f32,
2380 px: f32,
2381 text: &str,
2382 ) -> Vec<Vec<Vec<[f32; 2]>>> {
2383 let font = &mut self.fonts[id];
2384 let asc = font.ascent();
2385 let tol = 0.3 / px;
2386 let mut pen = 0.0f32;
2387 let mut glyphs = Vec::new();
2388 for ch in text.chars() {
2389 let go = font.glyph_outline(ch, tol);
2390 let mut contours = Vec::with_capacity(go.polylines.len());
2391 for pl in &go.polylines {
2392 let mapped: Vec<[f32; 2]> = pl
2393 .iter()
2394 .map(|p| [x + (pen + p[0]) * px, y + (asc - p[1]) * px])
2395 .collect();
2396 contours.push(mapped);
2397 }
2398 glyphs.push(contours);
2399 pen += go.advance;
2400 }
2401 glyphs
2402 }
2403
2404 pub fn register_program(&mut self, program: &Program) -> Result<(), String> {
2409 for item in &program.items {
2410 self.register_item("", item)?;
2411 }
2412 let mut env = new_env();
2413 let non_do: Vec<_> = self
2414 .globals
2415 .iter()
2416 .filter(|(_, e)| !matches!(e, Expr::Do(_)))
2417 .map(|(k, e)| (k.clone(), e.clone()))
2418 .collect();
2419 let mut pending: Vec<(String, Expr)> = Vec::new();
2420 for (k, expr) in &non_do {
2421 let mut tmp = new_env();
2422 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2423 env.insert(k.clone(), v);
2424 } else {
2425 pending.push((k.clone(), expr.clone()));
2426 }
2427 }
2428 for (k, expr) in &pending {
2429 let mut tmp = env.clone();
2430 if let Ok(v) = self.eval_expr(expr, &mut tmp) {
2431 env.insert(k.clone(), v);
2432 }
2433 }
2434 self.global_seed = env;
2435 Ok(())
2436 }
2437
2438 pub fn run_program(&mut self, program: &Program) -> Result<(), String> {
2439 self.register_program(program)?;
2440 let entry = self
2441 .find_entry()
2442 .ok_or("no entry point — need `bind start = do {...}` or `ผูก เริ่ม = ทำ {...}`")?;
2443 let mut env = self.global_seed.clone();
2444 self.framed("start", |me| me.eval_expr(&entry, &mut env))
2445 .map(|_| ())
2446 .map_err(|e| match e {
2447 EvalErr::Runtime(s) => s,
2448 EvalErr::Return(_) => "unexpected top-level return".to_string(),
2449 EvalErr::Break => "unexpected break at top level".to_string(),
2450 })
2451 }
2452
2453 fn register_item(&mut self, ns: &str, item: &Item) -> Result<(), String> {
2454 match item {
2455 Item::Bind(name, expr) => {
2456 let key = if ns.is_empty() {
2457 name.clone()
2458 } else {
2459 format!("{ns}::{name}")
2460 };
2461 self.globals.insert(key, expr.clone());
2462 },
2463 Item::Fn(def) => {
2464 let key = if ns.is_empty() {
2465 def.name.clone()
2466 } else {
2467 format!("{ns}::{}", def.name)
2468 };
2469 self.functions.insert(key, Rc::new(def.clone()));
2470 },
2471 Item::Mod(name, body) => {
2472 let child_ns = if ns.is_empty() {
2473 name.clone()
2474 } else {
2475 format!("{ns}::{name}")
2476 };
2477 for child in body {
2478 self.register_item(&child_ns, child)?;
2479 }
2480 },
2481 Item::TypeAlias(_, _) => {},
2482 Item::Struct(name, fields) => {
2483 self.structs.insert(name.clone(), fields.clone());
2484 if !ns.is_empty() {
2485 self.structs.insert(format!("{ns}::{name}"), fields.clone());
2486 }
2487 },
2488 Item::Enum(name, variants) => {
2489 for v in variants {
2490 self.enum_variants
2491 .insert(v.name.clone(), (name.clone(), v.arity));
2492 self.enum_variants
2493 .insert(format!("{name}::{}", v.name), (name.clone(), v.arity));
2494 if !ns.is_empty() {
2495 self.enum_variants
2496 .insert(format!("{ns}::{name}::{}", v.name), (name.clone(), v.arity));
2497 }
2498 }
2499 },
2500 Item::Use { path, alias } => {
2501 self.load_module(path, alias.as_deref(), ns)?;
2502 },
2503 }
2504 Ok(())
2505 }
2506
2507 fn load_module(
2512 &mut self,
2513 path: &str,
2514 alias: Option<&str>,
2515 parent_ns: &str,
2516 ) -> Result<(), String> {
2517 #[cfg(target_arch = "wasm32")]
2519 let (source, sub_dir) = {
2520 if self.loaded_files.contains(path) {
2522 return Ok(());
2523 }
2524 self.loaded_files.insert(path.to_string());
2525
2526 let src = crate::runtime::get_wasm_module(path)
2527 .or_else(|| crate::runtime::get_wasm_module(&format!("{}.ling", path)))
2528 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2529 (src, None::<std::path::PathBuf>)
2530 };
2531
2532 #[cfg(not(target_arch = "wasm32"))]
2534 let (source, sub_dir) = {
2535 let base_dir = self
2536 .source_dir
2537 .clone()
2538 .unwrap_or_else(|| std::path::PathBuf::from("."));
2539 let raw = std::path::Path::new(path);
2540 let candidates: Vec<std::path::PathBuf> = vec![
2541 base_dir.join(format!("{}.ling", path)),
2542 base_dir.join(format!("{}.灵", path)),
2543 base_dir.join(format!("{}.령", path)),
2544 base_dir.join(format!("{}.霊", path)),
2545 base_dir.join(format!("{}.ลิง", path)),
2546 base_dir.join(raw),
2547 std::path::PathBuf::from(format!("{}.ling", path)),
2548 std::path::PathBuf::from(path),
2549 ];
2550
2551 let resolved = candidates
2552 .into_iter()
2553 .find(|p| p.exists())
2554 .ok_or_else(|| format!("use: cannot find module '{path}'"))?;
2555
2556 let canonical = resolved
2557 .canonicalize()
2558 .unwrap_or_else(|_| resolved.clone())
2559 .to_string_lossy()
2560 .to_string();
2561
2562 if self.loaded_files.contains(&canonical) {
2564 return Ok(());
2565 }
2566 self.loaded_files.insert(canonical.clone());
2567
2568 let src = std::fs::read_to_string(&resolved)
2569 .map_err(|e| format!("use: failed to read '{path}': {e}"))?;
2570 let dir = resolved.parent().map(|p| p.to_path_buf());
2571 (src, dir)
2572 };
2573
2574 let program = crate::parser::parse(&source)
2575 .map_err(|e| format!("use: parse error in '{path}': {e}"))?;
2576
2577 let target_ns = match (parent_ns.is_empty(), alias) {
2579 (_, Some(a)) if !parent_ns.is_empty() => format!("{parent_ns}::{a}"),
2580 (_, Some(a)) => a.to_string(),
2581 (false, None) => parent_ns.to_string(),
2582 (true, None) => String::new(),
2583 };
2584
2585 let prev_dir = self.source_dir.clone();
2587 self.source_dir = sub_dir;
2588
2589 for item in &program.items {
2590 self.register_item(&target_ns, item)?;
2591 }
2592
2593 self.source_dir = prev_dir;
2594 Ok(())
2595 }
2596
2597 fn find_entry(&self) -> Option<Expr> {
2598 for key in crate::entry::ENTRY_NAMES {
2600 if let Some(e) = self.globals.get(*key) {
2601 return Some(e.clone());
2602 }
2603 }
2604 self.globals
2605 .values()
2606 .find(|e| matches!(e, Expr::Do(_)))
2607 .cloned()
2608 }
2609
2610 fn eval_expr(&mut self, expr: &Expr, env: &mut Env) -> EvalResult {
2613 match expr {
2614 Expr::Str(s) => Ok(Value::Str(s.clone())),
2615 Expr::Number(n) => Ok(Value::Number(*n)),
2616 Expr::Bool(b) => Ok(Value::Bool(*b)),
2617 Expr::Unit => Ok(Value::Unit),
2618 Expr::Array(elems) => {
2619 let vs: Vec<_> = elems
2620 .iter()
2621 .map(|e| self.eval_expr(e, env))
2622 .collect::<Result<_, _>>()?;
2623 Ok(Value::List(Rc::new(vs)))
2624 },
2625
2626 Expr::Ident(name) => self.lookup(name, env),
2627
2628 Expr::Path(segs) => {
2629 if segs.len() == 1 {
2630 return self.lookup(&segs[0], env);
2631 }
2632 Ok(Value::Str(segs.join("::")))
2633 },
2634
2635 Expr::Ref(inner) => self.eval_expr(inner, env),
2636 Expr::Await(inner) => self.eval_expr(inner, env),
2637
2638 Expr::Do(stmts) => {
2639 let mut local = env.clone();
2640 Ok(self.exec_block(stmts, &mut local)?.unwrap_or(Value::Unit))
2641 },
2642
2643 Expr::BinOp(op, lhs, rhs) => {
2644 let l = self.eval_expr(lhs, env)?;
2645 let r = self.eval_expr(rhs, env)?;
2646 self.apply_binop(op, l, r)
2647 },
2648
2649 Expr::If { cond, then, elseifs, else_body } => {
2650 let cond_val = self.eval_expr(cond, env)?;
2651 if self.is_truthy(&cond_val) {
2652 return Ok(self.exec_block(then, env)?.unwrap_or(Value::Unit));
2653 }
2654 for (ei_cond, ei_body) in elseifs {
2655 let ei_cond_val = self.eval_expr(ei_cond, env)?;
2656 if self.is_truthy(&ei_cond_val) {
2657 return Ok(self.exec_block(ei_body, env)?.unwrap_or(Value::Unit));
2658 }
2659 }
2660 if let Some(eb) = else_body {
2661 return Ok(self.exec_block(eb, env)?.unwrap_or(Value::Unit));
2662 }
2663 Ok(Value::Unit)
2664 },
2665
2666 Expr::While { cond, body } => {
2667 loop {
2671 let cv = self.eval_expr(cond, env)?;
2672 if !self.is_truthy(&cv) {
2673 break;
2674 }
2675 match self.exec_block(body, env) {
2676 Ok(_) => {},
2677 Err(EvalErr::Break) => break,
2678 Err(e) => return Err(e),
2679 }
2680 }
2681 Ok(Value::Unit)
2682 },
2683
2684 Expr::For { var, iter, body } => {
2685 let iter_val = self.eval_expr(iter, env)?;
2686 let items = self.value_to_iter(iter_val)?;
2687 for item in items {
2688 let mut local = env.clone();
2689 local.insert(var.clone(), item);
2690 match self.exec_block(body, &mut local) {
2691 Ok(_) => {},
2692 Err(EvalErr::Break) => break,
2693 Err(e) => return Err(e),
2694 }
2695 }
2696 Ok(Value::Unit)
2697 },
2698
2699 Expr::Match(subject, arms) => {
2700 let subj = self.eval_expr(subject, env)?;
2701 for arm in arms {
2702 if let Some(bindings) = self.match_pattern(&arm.pattern, &subj) {
2703 let mut local = env.clone();
2704 local.extend(bindings);
2705 return self.eval_expr(&arm.body, &mut local);
2706 }
2707 }
2708 Ok(Value::Unit)
2709 },
2710
2711 Expr::Range(lo, hi) => {
2712 let lo_v = self.eval_expr(lo, env)?;
2713 let hi_v = self.eval_expr(hi, env)?;
2714 let lo_n = self.to_number(&lo_v)? as i64;
2715 let hi_n = self.to_number(&hi_v)? as i64;
2716 Ok(Value::List(Rc::new(
2717 (lo_n..hi_n).map(|i| Value::Number(i as f64)).collect(),
2718 )))
2719 },
2720
2721 Expr::Index(base, idx) => {
2722 let b = self.eval_expr(base, env)?;
2723 let i = self.eval_expr(idx, env)?;
2724 let n = self.to_number(&i)? as usize;
2725 match b {
2726 Value::List(v) => v
2727 .get(n)
2728 .cloned()
2729 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2730 Value::Str(s) => s
2731 .chars()
2732 .nth(n)
2733 .map(|c| Value::Str(c.to_string()))
2734 .ok_or_else(|| EvalErr::from(format!("index {n} out of bounds"))),
2735 other => Err(EvalErr::from(format!("cannot index {:?}", other))),
2736 }
2737 },
2738
2739 Expr::Call(callee, args) => {
2740 let arg_vals: Vec<Value> = args
2741 .iter()
2742 .map(|a| self.eval_expr(a, env))
2743 .collect::<Result<_, _>>()?;
2744 match callee.as_ref() {
2745 Expr::Ident(name) => self.call_named(name, arg_vals, env),
2746 Expr::Path(segs) => self.call_named(&segs.join("::"), arg_vals, env),
2747 _ => {
2748 let v = self.eval_expr(callee, env)?;
2749 self.call_value(v, arg_vals)
2750 },
2751 }
2752 },
2753
2754 Expr::MethodCall { receiver, method, args } => {
2755 let recv = self.eval_expr(receiver, env)?;
2756 let arg_vals: Vec<Value> = args
2757 .iter()
2758 .map(|a| self.eval_expr(a, env))
2759 .collect::<Result<_, _>>()?;
2760 self.call_method(recv, method, arg_vals)
2761 },
2762
2763 Expr::Closure(params, body) => Ok(Value::Fn(
2764 params.clone(),
2765 vec![Stmt::Expr(*body.clone())],
2766 env.clone(),
2767 )),
2768
2769 Expr::Asm(_) => Ok(Value::Unit),
2770 }
2771 }
2772
2773 fn exec_block(&mut self, stmts: &[Stmt], env: &mut Env) -> Result<Option<Value>, EvalErr> {
2776 let mut last: Option<Value> = None;
2777 for stmt in stmts {
2778 match stmt {
2779 Stmt::Bind(name, expr) => {
2780 match self.try_inplace_list_update(name, expr, env)? {
2781 Some(v) => env.insert(name.clone(), v),
2782 None => {
2783 let v = self.eval_expr(expr, env)?;
2784 env.insert(name.clone(), v)
2785 },
2786 };
2787 last = None;
2788 },
2789 Stmt::Return(expr) => {
2790 let v = self.eval_expr(expr, env)?;
2791 return Err(EvalErr::Return(v));
2792 },
2793 Stmt::Expr(expr) => {
2794 last = Some(self.eval_expr(expr, env)?);
2795 },
2796 }
2797 }
2798 Ok(last)
2799 }
2800
2801 fn try_inplace_list_update(
2808 &mut self,
2809 name: &str,
2810 expr: &Expr,
2811 env: &mut Env,
2812 ) -> Result<Option<Value>, EvalErr> {
2813 let Expr::Call(callee, args) = expr else { return Ok(None) };
2814 let Expr::Ident(fname) = callee.as_ref() else { return Ok(None) };
2815 let is_push = matches!(
2816 fname.as_str(),
2817 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가"
2818 );
2819 let is_set = matches!(
2820 fname.as_str(),
2821 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정"
2822 );
2823 if !is_push && !is_set {
2824 return Ok(None);
2825 }
2826 match args.first() {
2829 Some(Expr::Ident(a0)) if a0 == name => {},
2830 _ => return Ok(None),
2831 }
2832 if self.functions.contains_key(fname.as_str()) {
2833 return Ok(None);
2834 }
2835 if is_push {
2836 if args.len() != 2 {
2837 return Ok(None);
2838 }
2839 let val = self.eval_expr(&args[1], env)?;
2840 match env.remove(name) {
2841 Some(Value::List(mut v)) => {
2842 Rc::make_mut(&mut v).push(val);
2843 Ok(Some(Value::List(v)))
2844 },
2845 other => {
2846 if let Some(o) = other {
2847 env.insert(name.to_string(), o);
2848 }
2849 Ok(None)
2850 },
2851 }
2852 } else {
2853 if args.len() != 3 {
2854 return Ok(None);
2855 }
2856 let idx_v = self.eval_expr(&args[1], env)?;
2857 let idx = self.to_number(&idx_v).unwrap_or(0.0) as usize;
2858 let val = self.eval_expr(&args[2], env)?;
2859 match env.remove(name) {
2860 Some(Value::List(mut v)) => {
2861 if idx < v.len() {
2862 Rc::make_mut(&mut v)[idx] = val;
2863 }
2864 Ok(Some(Value::List(v)))
2865 },
2866 other => {
2867 if let Some(o) = other {
2868 env.insert(name.to_string(), o);
2869 }
2870 Ok(None)
2871 },
2872 }
2873 }
2874 }
2875
2876 fn lookup(&self, name: &str, env: &Env) -> EvalResult {
2879 if let Some(v) = env.get(name) {
2880 return Ok(v.clone());
2881 }
2882 if let Some(v) = self.global_seed.get(name) {
2885 return Ok(v.clone());
2886 }
2887 if self.functions.contains_key(name) {
2888 let def = &self.functions[name];
2889 return Ok(Value::Fn(def.params.clone(), def.body.clone(), new_env()));
2890 }
2891 if let Some((enum_name, 0)) = self.enum_variants.get(name).cloned() {
2893 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
2894 return Ok(Value::Variant { enum_name, variant, payload: Vec::new() });
2895 }
2896 match name {
2898 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی" | "пи" => {
2899 return Ok(Value::Number(std::f64::consts::PI))
2900 },
2901 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
2902 return Ok(Value::Number(std::f64::consts::TAU))
2903 },
2904 _ => {},
2905 }
2906 Err(EvalErr::from(format!("undefined: '{name}'")))
2907 }
2908
2909 pub(crate) fn call_named(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2918 if !ling_profile_enabled() {
2919 return self.call_named_inner(name, args, env);
2920 }
2921 let t0 = crate::runtime::now_secs();
2922 let r = self.call_named_inner(name, args, env);
2923 ling_profile_record(
2924 name,
2925 ((crate::runtime::now_secs() - t0) * 1_000_000_000.0) as u128,
2926 );
2927 r
2928 }
2929
2930 fn call_named_inner(&mut self, name: &str, args: Vec<Value>, env: &Env) -> EvalResult {
2931 if let Some(def) = self.functions.get(name).cloned() {
2934 let mut call_env =
2935 FxHashMap::with_capacity_and_hasher(def.params.len(), Default::default());
2936 let _ = env; for (param, arg) in def.params.iter().zip(args) {
2938 call_env.insert(param.clone(), arg);
2939 }
2940 return match self.framed(name, |me| me.exec_block(&def.body, &mut call_env)) {
2941 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
2942 Err(EvalErr::Return(v)) => Ok(v),
2943 Err(e) => Err(e),
2944 };
2945 }
2946
2947 #[cfg(target_arch = "wasm32")]
2948 if let Some(v) = self.wasm_music_builtin(name, &args)? {
2949 return Ok(v);
2950 }
2951
2952 match name {
2953 "__ling_global" => {
2956 if let Some(Value::Str(g)) = args.first() {
2957 if let Some(v) = self.global_seed.get(g.as_str()) {
2958 return Ok(v.clone());
2959 }
2960 }
2961 return Ok(Value::Unit);
2962 },
2963 "print" | "println" | "印" | "打印" | "印刷" | "พิมพ์" | "출력" | "вывести"
2965 | "imprimir" | "afficher" | "چاپ" | "اطبع" | "הדפס" | "چھاپو" | "drucken" | "печать" => {
2966 let s = args
2967 .iter()
2968 .map(|v| v.to_string())
2969 .collect::<Vec<_>>()
2970 .join("");
2971 println!("{s}");
2972 return Ok(Value::Unit);
2973 },
2974 "print_color" | "พิมพ์สี" | "چاپ_رنگی" | "اطبع_بلون" | "הדפס_בצבע" | "رنگین_چھاپو" => {
2977 #[cfg(windows)]
2978 {
2979 use std::sync::Once;
2980 static VT: Once = Once::new();
2981 VT.call_once(|| {
2982 extern "system" {
2983 fn GetStdHandle(n: u32) -> *mut std::ffi::c_void;
2984 fn GetConsoleMode(h: *mut std::ffi::c_void, m: *mut u32) -> i32;
2985 fn SetConsoleMode(h: *mut std::ffi::c_void, m: u32) -> i32;
2986 }
2987 unsafe {
2988 let h = GetStdHandle(0xFFFF_FFF5u32); let mut mode = 0u32;
2990 if GetConsoleMode(h, &mut mode) != 0 {
2991 SetConsoleMode(h, mode | 0x0004); }
2993 }
2994 });
2995 }
2996 let col = self.arg_num(&args, 0, 7.0)? as i64;
2997 let s = args
2998 .iter()
2999 .skip(1)
3000 .map(|v| v.to_string())
3001 .collect::<Vec<_>>()
3002 .join("");
3003 let code = 90 + col.clamp(0, 7);
3004 println!("\x1b[1;{code}m{s}\x1b[0m");
3005 return Ok(Value::Unit);
3006 },
3007 "format"
3009 | "格式"
3010 | "フォーマット"
3011 | "서식"
3012 | "รูปแบบ"
3013 | "форматировать"
3014 | "formatear"
3015 | "formater" | "قالببندی" | "نسّق" | "פרמט" | "فارمیٹ" | "formatieren" => {
3016 return Ok(Value::Str(self.builtin_format(&args)?));
3017 },
3018 "格式::拼接" | "format::join" => match args.first() {
3020 Some(Value::List(items)) => {
3021 return Ok(Value::Str(items.iter().map(|v| v.to_string()).collect()));
3022 },
3023 _ => return Ok(Value::Str(self.builtin_format(&args)?)),
3024 },
3025 "ok" | "好" | "良し" | "좋아" | "โอเค" | "تایید" | "تمام" | "בסדר" | "ٹھیک" | "bon" | "gut" | "хорошо" => {
3027 let val = args.into_iter().next().unwrap_or(Value::Unit);
3028 return Ok(Value::Ok(Box::new(val)));
3029 },
3030 "bad" | "坏" | "err" | "悪い" | "나쁨" | "ผิด" | "بد" | "سيء" | "רע" | "برا" | "mauvais" | "schlecht" | "плохо" => {
3031 let val = args.into_iter().next().unwrap_or(Value::Unit);
3032 return Ok(Value::Err(Box::new(val)));
3033 },
3034 "向量::从" | "Vec::from" => {
3036 if let Some(Value::List(v)) = args.first() {
3037 return Ok(Value::List(v.clone()));
3038 }
3039 return Ok(Value::List(Rc::new(args)));
3040 },
3041 "向量::有容量" | "Vec::with_capacity" => {
3042 return Ok(Value::List(Rc::new(Vec::new())))
3043 },
3044 "计时::获取当前小时" | "Timer::hour" => return Ok(Value::Number(14.0)),
3046 "计时::现在" | "Timer::now" => return Ok(Value::Number(1000.0)),
3047 "sleep" | "หยุด" | "นอน" | "sleep_ms" | "睡眠" | "眠る" | "スリープ" | "잠자기"
3049 | "잠" | "流水::睡眠" | "Flow::sleep" | "خواب" | "نم" | "שינה" | "سو_جاؤ" | "dormir" | "schlafen" | "спать" => {
3050 if let Some(ms_val) = args.first() {
3051 if let Ok(ms) = self.to_number(ms_val) {
3052 #[cfg(target_arch = "wasm32")]
3053 wasm_sleep_ms(ms.max(0.0) as i32);
3054 #[cfg(not(target_arch = "wasm32"))]
3055 std::thread::sleep(std::time::Duration::from_millis(ms as u64));
3056 }
3057 }
3058 return Ok(Value::Unit);
3059 },
3060 "流水::并行" | "Flow::parallel" => {
3062 if let Some(Value::Fn(params, body, mut cap)) = args.first().cloned() {
3063 let _ = params;
3064 match self.exec_block(&body, &mut cap) {
3065 Ok(Some(v)) => return Ok(v),
3066 Ok(None) => return Ok(Value::Unit),
3067 Err(EvalErr::Return(v)) => return Ok(v),
3068 Err(e) => return Err(e),
3069 }
3070 }
3071 return Ok(Value::Unit);
3072 },
3073
3074 "sin" | "ไซน์" | "正弦" | "サイン" | "사인" | "سینوس" | "جا" | "סינוס" | "سائن" | "sinus" | "синус" => {
3083 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sin()));
3084 },
3085 "cos" | "โคไซน์" | "余弦" | "コサイン" | "코사인" | "کسینوس" | "جتا" | "קוסינוס" | "کوسائن" | "cosinus" | "kosinus" | "косинус" => {
3086 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cos()));
3087 },
3088
3089 "tanh" | "tanhf" | "双曲正切" | "双曲線正接" | "쌍곡탄젠트" => {
3092 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tanh()));
3093 },
3094
3095 "tan" | "แทนเจนต์" | "正切" | "タンジェント" | "탄젠트" | "تانژانت" | "ظا" | "טנגנס" | "ٹینجنٹ" | "tangente" | "tangens" | "тангенс" => {
3096 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.tan()));
3097 },
3098 "asin" | "arcsin" | "反正弦" | "アークサイン" | "아크사인" | "อาร์กไซน์" | "آرکسینوس" | "قوس_جا" | "ארקסינוס" | "آرک_سائن" | "arcsinus" | "arkussinus" | "арксинус" =>
3099 {
3100 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.asin()));
3101 },
3102 "acos" | "arccos" | "反余弦" | "アークコサイン" | "아크코사인" | "อาร์กโคไซน์" | "آرککسینوس" | "قوس_جتا" | "ארקוקוסינוס" | "آرک_کوسائن" | "arccosinus" | "arkuskosinus" | "арккосинус" =>
3103 {
3104 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.acos()));
3105 },
3106 "atan" | "arctan" | "反正切" | "アークタンジェント" | "아크탄젠트" | "อาร์กแทนเจนต์" | "آرکتانژانت" | "قوس_ظا" | "ארקטנגנס" | "آرک_ٹینجنٹ" | "arctangente" | "arkustangens" | "арктангенс" =>
3107 {
3108 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.atan()));
3109 },
3110 "atan2" | "arctan2" | "反正切2" | "アークタンジェント2" | "아크탄젠트2" =>
3111 {
3112 let y = self.arg_num(&args, 0, 0.0)?;
3113 let x = self.arg_num(&args, 1, 1.0)?;
3114 return Ok(Value::Number(y.atan2(x)));
3115 },
3116
3117 "sqrt" | "รากที่สอง" | "平方根" | "根" | "제곱근" | "جذر" | "جذر" | "שורש" | "جذر" | "racine_carrée" | "quadratwurzel" | "корень" => {
3119 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.sqrt()));
3120 },
3121 "cbrt" | "立方根" | "세제곱근" | "รากที่สาม" | "ریشه_سوم" | "جذر_تكعيبي" | "שורש_שלישי" | "مکعب_جذر" | "racine_cubique" | "kubikwurzel" | "кубический_корень" => {
3122 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.cbrt()));
3123 },
3124 "pow" | "ยกกำลัง" | "幂" | "べき乗" | "거듭제곱" | "توان" | "أس" | "חזקה" | "قوت" | "puissance" | "potenz" | "степень" => {
3125 let base = self.arg_num(&args, 0, 0.0)?;
3126 let exp = self.arg_num(&args, 1, 1.0)?;
3127 return Ok(Value::Number(base.powf(exp)));
3128 },
3129 "exp" | "指数" | "指数関数" | "지수" => {
3130 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.exp()));
3131 },
3132 "hypot" | "斜边" | "斜辺" | "빗변" => {
3133 let x = self.arg_num(&args, 0, 0.0)?;
3134 let y = self.arg_num(&args, 1, 0.0)?;
3135 return Ok(Value::Number(x.hypot(y)));
3136 },
3137
3138 "ln" | "log" | "ลอการิทึม" | "对数" | "対数" | "로그" | "لگاریتم_طبیعی" | "لوغاريتم_طبيعي" | "לוגריתם_טבעי" | "فطری_لوگ" => {
3140 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.ln()));
3141 },
3142 "log2" | "对数2" | "対数2" | "로그2" => {
3143 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log2()));
3144 },
3145 "log10" | "对数10" | "対数10" | "로그10" => {
3146 return Ok(Value::Number(self.arg_num(&args, 0, 1.0)?.log10()));
3147 },
3148
3149 "abs" | "ค่าสัมบูรณ์" | "绝对值" | "绝对" | "絶対値" | "절댓값" | "절대값" | "قدرمطلق" | "مطلق" | "ערך_מוחלט" | "مطلق_قدر" | "valeur_absolue" | "betrag" | "модуль_числа" =>
3151 {
3152 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.abs()));
3153 },
3154 "floor" | "ปัดลง" | "向下取整" | "下整" | "床関数" | "내림" | "کف" | "أرضية" | "רצפה" | "فرش" | "plancher" | "abrunden" | "вниз" => {
3155 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.floor()));
3156 },
3157 "ceil" | "ปัดขึ้น" | "向上取整" | "上整" | "天井関数" | "올림" | "سقف" | "سقف" | "תקרה" | "چھت" | "plafond" | "aufrunden" | "вверх" =>
3158 {
3159 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.ceil()));
3160 },
3161 "round" | "ปัดเศษ" | "四舍五入" | "四舍" | "四捨五入" | "반올림" | "گرد_کردن" | "تقريب" | "עיגול" | "گول" | "arrondir" | "runden" | "округлить" =>
3162 {
3163 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.round()));
3164 },
3165 "trunc"
3166 | "int"
3167 | "ตัดทศนิยม"
3168 | "取整"
3169 | "整数化"
3170 | "整数"
3171 | "截整"
3172 | "정수화"
3173 | "정수"
3174 | "切り捨て"
3175 | "버림" | "برش" | "اقتطاع" | "קיטום" | "کٹائی" | "tronquer" | "abschneiden" | "усечь" => {
3176 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.trunc()));
3177 },
3178 "fract" | "小数部分" | "小数部" | "소수부" => {
3179 return Ok(Value::Number(self.arg_num(&args, 0, 0.0)?.fract()));
3180 },
3181
3182 "min" | "ต่ำสุด" | "最小" | "최솟값" | "کمینه" | "أصغر" | "מינימום" | "کم_ترین" | "minimum" | "минимум" => {
3184 let a = self.arg_num(&args, 0, 0.0)?;
3185 let b = self.arg_num(&args, 1, 0.0)?;
3186 return Ok(Value::Number(a.min(b)));
3187 },
3188 "max" | "สูงสุด" | "最大" | "최댓값" | "بیشینه" | "أكبر" | "מקסימום" | "زیادہ_ترین" | "maximum" | "максимум" => {
3189 let a = self.arg_num(&args, 0, 0.0)?;
3190 let b = self.arg_num(&args, 1, 0.0)?;
3191 return Ok(Value::Number(a.max(b)));
3192 },
3193 "clamp" | "จำกัด" | "截取" | "範囲制限" | "범위제한" | "محدود" | "قيّد" | "הגבל" | "محدود" | "limiter" | "begrenzen" | "ограничить" => {
3194 let x = self.arg_num(&args, 0, 0.0)?;
3195 let lo = self.arg_num(&args, 1, 0.0)?;
3196 let hi = self.arg_num(&args, 2, 1.0)?;
3197 return Ok(Value::Number(x.clamp(lo, hi)));
3198 },
3199
3200 "pi" | "π" | "พาย" | "圆周率" | "円周率" | "파이" | "پی" | "باي" | "פאי" | "پائی" | "пи" => {
3202 return Ok(Value::Number(std::f64::consts::PI))
3203 },
3204 "tau" | "τ" | "双周率" | "タウ" | "타우" | "ทาว" | "تاو" | "تاو" | "טאו" | "ٹاؤ" | "тау" => {
3205 return Ok(Value::Number(std::f64::consts::TAU))
3206 },
3207
3208 "vnoise" | "noise2" | "นอยส์2ดี" | "柏林噪声2D" | "バリューノイズ2D" | "값노이즈2D" | "نویز_برداری" | "ضجيج_متجه" | "רעש_וקטורי" | "ویکٹر_نوائز" | "bruit_v" | "v_rauschen" | "шум_v" =>
3214 {
3215 let x = self.arg_num(&args, 0, 0.0)? as f32;
3216 let y = self.arg_num(&args, 1, 0.0)? as f32;
3217 let seed = self.arg_num(&args, 2, 0.0)? as u32;
3218 return Ok(Value::Number(tex_vnoise(x, y, seed) as f64));
3219 },
3220
3221 "fbm" | "นอยส์ออร์แกนิก" | "分形噪声" | "フラクタルノイズ" | "프랙탈노이즈" | "نویز_فراکتالی" | "ضجيج_عضوي" | "רעש_פרקטלי" | "فریکٹل_نوائز" =>
3222 {
3223 let x = self.arg_num(&args, 0, 0.0)? as f32;
3224 let y = self.arg_num(&args, 1, 0.0)? as f32;
3225 let octaves = self.arg_num(&args, 2, 4.0)? as u32;
3226 let seed = self.arg_num(&args, 3, 0.0)? as u32;
3227 return Ok(Value::Number(tex_fbm(x, y, octaves, seed) as f64));
3228 },
3229
3230 "perlin"
3231 | "perlin3"
3232 | "เพอร์ลิน3ดี"
3233 | "柏林噪声3D"
3234 | "パーリンノイズ3D"
3235 | "펄린노이즈3D" | "نویز_پرلین" | "بيرلين" | "רעש_פרלין" | "پرلن_نوائز" | "перлин" => {
3236 let x = self.arg_num(&args, 0, 0.0)? as f32;
3237 let y = self.arg_num(&args, 1, 0.0)? as f32;
3238 let z = self.arg_num(&args, 2, 0.0)? as f32;
3239 return Ok(Value::Number(perlin3(x, y, z) as f64));
3240 },
3241
3242 "lerp" | "ค่าระหว่าง" | "线性插值" | "線形補間" | "선형보간" | "میانیابی" | "استيفاء" | "אינטרפולציה" | "درمیانی_قدر" | "interpoler" | "interpolieren" | "интерполировать" =>
3244 {
3245 let a = self.arg_num(&args, 0, 0.0)?;
3246 let b = self.arg_num(&args, 1, 1.0)?;
3247 let t = self.arg_num(&args, 2, 0.0)?;
3248 return Ok(Value::Number(a + (b - a) * t));
3249 },
3250
3251 "smoothstep" | "เปลี่ยนแบบนุ่ม" | "平滑步进" | "スムーズステップ" | "스무스스텝" | "گام_نرم" | "تدرج_ناعم" | "מדרגה_חלקה" | "ہموار_قدم" | "lissage" | "glättung" | "сглаживание" =>
3252 {
3253 let lo = self.arg_num(&args, 0, 0.0)?;
3254 let hi = self.arg_num(&args, 1, 1.0)?;
3255 let x = self.arg_num(&args, 2, 0.5)?;
3256 let t = ((x - lo) / (hi - lo)).clamp(0.0, 1.0);
3257 return Ok(Value::Number(t * t * (3.0 - 2.0 * t)));
3258 },
3259
3260 "rand" | "สุ่ม" | "随机" | "乱数" | "난수" | "تصادفی" | "عشوائي" | "אקראי" | "بے_ترتیب" | "aléatoire" | "zufall" | "случайное" => {
3261 let val = fast_rand_f64(&mut self.rand_state);
3262 return Ok(Value::Number(val));
3263 },
3264
3265 "sign" | "เครื่องหมาย" | "符号" | "符号関数" | "부호" | "علامت" | "إشارة" | "סימן" | "نشان" | "signe" | "vorzeichen" | "знак" => {
3266 let x = self.arg_num(&args, 0, 0.0)?;
3267 return Ok(Value::Number(x.signum()));
3268 },
3269
3270 "hsv_to_rgb" | "เอชเอสวีเป็นRGB" | "HSV转RGB" | "HSV変換RGB" | "HSV변환RGB" | "HSV_به_RGB" | "HSV_إلى_RGB" | "HSV_ל_RGB" | "HSV_سے_RGB" | "hsv_vers_rgb" | "hsv_zu_rgb" | "hsv_в_rgb" =>
3271 {
3272 let h = self.arg_num(&args, 0, 0.0)?; let s = self.arg_num(&args, 1, 1.0)?; let v = self.arg_num(&args, 2, 1.0)?; let c = v * s;
3276 let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
3277 let m = v - c;
3278 let (r1, g1, b1) = if h < 60.0 {
3279 (c, x, 0.0)
3280 } else if h < 120.0 {
3281 (x, c, 0.0)
3282 } else if h < 180.0 {
3283 (0.0, c, x)
3284 } else if h < 240.0 {
3285 (0.0, x, c)
3286 } else if h < 300.0 {
3287 (x, 0.0, c)
3288 } else {
3289 (c, 0.0, x)
3290 };
3291 let r = ((r1 + m) * 255.0).round();
3292 let g = ((g1 + m) * 255.0).round();
3293 let b = ((b1 + m) * 255.0).round();
3294 return Ok(Value::List(Rc::new(vec![
3295 Value::Number(r),
3296 Value::Number(g),
3297 Value::Number(b),
3298 ])));
3299 },
3300
3301 "lerp_color" | "ไล่สี" | "颜色插值" | "色補間" | "색보간" | "میانیابی_رنگ" | "استيفاء_اللون" | "אינטרפולציית_צבע" | "رنگ_درمیانی_قدر" | "interpoler_couleur" | "farbe_interpolieren" | "интерполировать_цвет" => {
3302 let r1 = self.arg_num(&args, 0, 0.0)?;
3303 let g1 = self.arg_num(&args, 1, 0.0)?;
3304 let b1 = self.arg_num(&args, 2, 0.0)?;
3305 let r2 = self.arg_num(&args, 3, 255.0)?;
3306 let g2 = self.arg_num(&args, 4, 255.0)?;
3307 let b2 = self.arg_num(&args, 5, 255.0)?;
3308 let t = self.arg_num(&args, 6, 0.0)?;
3309 let r = r1 + (r2 - r1) * t;
3310 let g = g1 + (g2 - g1) * t;
3311 let b = b1 + (b2 - b1) * t;
3312 let c = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
3313 self.gfx.borrow_mut().color = c;
3314 return Ok(Value::Unit);
3315 },
3316
3317 "time_now" | "เวลาปัจจุบัน" | "当前时间" | "経過時間" | "현재시간" | "زمان_اکنون" | "الوقت_الآن" | "הזמן_עכשיו" | "ابھی_کا_وقت" | "temps_actuel" | "aktuelle_zeit" | "текущее_время" =>
3319 {
3320 return Ok(Value::Number(
3321 crate::runtime::now_secs() - self.start_time_secs,
3322 ));
3323 },
3324
3325 "epoch_now" | "เวลาโลก" | "datetime" | "现在时刻" | "現在時刻" | "현재시각" | "مهر_زمانی_اکنون" | "طابع_الوقت_الآن" | "חותמת_זמן_עכשיו" | "ایپاک_وقت" =>
3329 {
3330 return Ok(Value::Number(crate::runtime::now_secs()));
3331 },
3332
3333 "frame_count" | "เฟรม" | "帧数" | "フレーム数" | "프레임수" | "شمار_فریم" | "عدد_الإطارات" | "ספירת_פריימים" | "فریم_شمار" | "nombre_images" | "bildanzahl" | "число_кадров" => {
3334 return Ok(Value::Number(self.frame_num as f64));
3335 },
3336
3337 "mic_open" | "เปิดไมค์" | "开麦克风" | "マイク開く" | "마이크열기" | "باز_کردن_میکروفون" | "افتح_الميكروفون" | "פתח_מיקרופון" | "مائیکروفون_کھولو" | "ouvrir_micro" | "mikrofon_öffnen" | "открыть_микрофон" =>
3339 {
3340 #[cfg(not(target_arch = "wasm32"))]
3341 {
3342 match ling_mic::MicInput::open(Default::default()) {
3343 Ok(mic) => {
3344 let _ = mic.start(|_samples: &[f32]| {}); self.mic = Some(mic);
3346 return Ok(Value::Number(1.0)); },
3348 Err(_e) => {
3351 self.mic = None;
3352 return Ok(Value::Number(0.0));
3353 },
3354 }
3355 }
3356 #[cfg(target_arch = "wasm32")]
3357 return Ok(Value::Unit);
3358 },
3359
3360 "mic_rms" | "เสียงRMS" | "麦克风音量" | "マイクRMS" | "마이크RMS" | "RMS_میکروفون" | "RMS_الميكروفون" | "RMS_מיקרופון" | "مائیکروفون_RMS" | "rms_micro" | "mikrofon_rms" | "rms_микрофона" =>
3361 {
3362 #[cfg(not(target_arch = "wasm32"))]
3363 {
3364 let rms = self
3365 .mic
3366 .as_ref()
3367 .map(|m: &ling_mic::MicInput| m.rms())
3368 .unwrap_or(0.0);
3369 return Ok(Value::Number(rms as f64));
3370 }
3371 #[cfg(target_arch = "wasm32")]
3372 return Ok(Value::Number(0.0));
3373 },
3374
3375 "mic_peak" | "เสียงพีค" | "麦克风峰值" | "マイクピーク" | "마이크피크" | "اوج_میکروفون" | "ذروة_الميكروفون" | "שיא_מיקרופון" | "مائیکروفون_چوٹی" | "crête_micro" | "mikrofon_spitze" | "пик_микрофона" =>
3376 {
3377 #[cfg(not(target_arch = "wasm32"))]
3378 {
3379 let peak = self
3380 .mic
3381 .as_ref()
3382 .map(|m: &ling_mic::MicInput| m.peak())
3383 .unwrap_or(0.0);
3384 return Ok(Value::Number(peak as f64));
3385 }
3386 #[cfg(target_arch = "wasm32")]
3387 return Ok(Value::Number(0.0));
3388 },
3389
3390 "mic_fft" | "วิเคราะห์เสียงสด" | "实时频谱" | "リアルタイムFFT" | "실시간FFT" | "FFT_میکروفون" | "FFT_الميكروفون" | "FFT_מיקרופון" | "مائیکروفون_FFT" | "fft_micro" | "mikrofon_fft" | "fft_микрофона" =>
3391 {
3392 #[cfg(not(target_arch = "wasm32"))]
3393 {
3394 let n = self.arg_num(&args, 0, 8.0)? as usize;
3395 if let Some(mic) = self.mic.as_ref() {
3396 let samples = mic.latest_samples();
3397 self.fft.borrow_mut().push_samples(&samples);
3398 }
3399 let bands = self.fft.borrow().freq_bands(n);
3400 let result: Vec<Value> =
3401 bands.iter().map(|&v| Value::Number(v as f64)).collect();
3402 return Ok(Value::List(Rc::new(result)));
3403 }
3404 #[cfg(target_arch = "wasm32")]
3405 return Ok(Value::List(Vec::new().into()));
3406 },
3407
3408 "set_blend" | "โหมดผสม" | "混合模式" | "ブレンドモード" | "블렌드모드" | "تنظیم_ترکیب" | "عيّن_المزج" | "קבע_מיזוג" | "بلینڈ_مقرر_کرو" | "définir_mélange" | "mischmodus_setzen" | "задать_смешивание" =>
3410 {
3411 let mode = self.arg_num(&args, 0, 0.0)? as u8;
3412 let mut gfx = self.gfx.borrow_mut();
3413 gfx.blend = mode;
3414 let a = gfx.alpha;
3415 gfx.depth_queue.set_state(mode, a); return Ok(Value::Unit);
3417 },
3418
3419 "set_antialias" | "ตั้งลบรอยหยัก" | "抗锯齿" | "アンチエイリアス" | "안티에일리어싱" | "تنظیم_ضدلبهدندانه" | "عيّن_مضاد_التسنن" | "קבע_החלקת_קצוות" | "اینٹی_الائیسنگ_مقرر_کرو" =>
3423 {
3424 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3425 self.gfx.borrow_mut().antialias = on;
3426 return Ok(Value::Unit);
3427 },
3428 "get_antialias"
3430 | "อ่านลบรอยหยัก"
3431 | "读取抗锯齿"
3432 | "アンチエイリアス取得"
3433 | "안티에일리어싱상태" | "خواندن_ضدلبهدندانه" | "اقرأ_مضاد_التسنن" | "קרא_החלקת_קצוות" | "اینٹی_الائیسنگ_پڑھو" => {
3434 return Ok(Value::Bool(self.gfx.borrow().antialias));
3435 },
3436
3437 "set_font_antialias" | "글꼴안티에일리어싱" => {
3441 let on = self.arg_num(&args, 0, 1.0)? > 0.5;
3442 self.gfx.borrow_mut().font_antialias = on;
3443 return Ok(Value::Unit);
3444 },
3445 "get_font_antialias" | "글꼴안티에일리어싱상태" => {
3447 return Ok(Value::Bool(self.gfx.borrow().font_antialias));
3448 },
3449
3450 "draw_circle" | "วาดวงกลม" | "画圆" | "円描画" | "원그리기" | "رسم_دایره" | "ارسم_دائرة" | "צייר_עיגול" | "دائرہ_کھینچو" | "dessiner_cercle" | "kreis_zeichnen" | "рисовать_круг" =>
3452 {
3453 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3454 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3455 let r = self.arg_num(&args, 2, 10.0)? as i32;
3456 let mut gfx = self.gfx.borrow_mut();
3457 let (w, h, color, blend) =
3458 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3459 if gfx.antialias {
3460 let (uw, uh) = (gfx.width, gfx.height);
3461 let segs = ((r.max(1) as u32) * 4).clamp(24, 512);
3462 crate::gfx::raster::draw_arc(
3463 &mut gfx.buffer,
3464 uw,
3465 uh,
3466 color,
3467 true,
3468 blend == 1,
3469 cx as f32,
3470 cy as f32,
3471 r as f32,
3472 0.0,
3473 std::f32::consts::TAU,
3474 segs,
3475 );
3476 } else {
3477 draw_circle_outline(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3478 }
3479 return Ok(Value::Unit);
3480 },
3481
3482 "draw_filled_circle"
3483 | "draw_disc"
3484 | "วาดวงกลมทึบ"
3485 | "画实心圆"
3486 | "塗りつぶし円"
3487 | "원채우기" | "رسم_دایره_توپر" | "ارسم_دائرة_ممتلئة" | "צייר_עיגול_מלא" | "بھرا_دائرہ_کھینچو" => {
3488 let cx = self.arg_num(&args, 0, 0.0)? as i32;
3489 let cy = self.arg_num(&args, 1, 0.0)? as i32;
3490 let r = self.arg_num(&args, 2, 10.0)? as i32;
3491 let mut gfx = self.gfx.borrow_mut();
3492 let (w, h, color, blend) =
3493 (gfx.width as i32, gfx.height as i32, gfx.color, gfx.blend);
3494 draw_circle_filled(&mut gfx.buffer, w, h, cx, cy, r, color, blend);
3495 return Ok(Value::Unit);
3496 },
3497
3498 "draw_arc" | "arc" | "วาดส่วนโค้ง" | "画弧" | "円弧描画" | "호그리기" | "رسم_کمان" | "ارسم_قوسا" | "צייר_קשת" | "آرک_کھینچو" =>
3502 {
3503 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3504 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3505 let r = self.arg_num(&args, 2, 10.0)? as f32;
3506 let a0 = self.arg_num(&args, 3, 0.0)? as f32;
3507 let a1 = self.arg_num(&args, 4, std::f64::consts::TAU)? as f32;
3508 let default_segs = ((r.abs() * (a1 - a0).abs()).ceil() as u32).clamp(8, 1024);
3509 let segs = self.arg_num(&args, 5, default_segs as f64)? as u32;
3510 let mut gfx = self.gfx.borrow_mut();
3511 let color = gfx.color;
3512 #[cfg(not(target_arch = "wasm32"))]
3513 {
3514 let (uw, uh, aa, add) = (gfx.width, gfx.height, gfx.antialias, gfx.blend == 1);
3515 crate::gfx::raster::draw_arc(
3516 &mut gfx.buffer,
3517 uw,
3518 uh,
3519 color,
3520 aa,
3521 add,
3522 cx,
3523 cy,
3524 r,
3525 a0,
3526 a1,
3527 segs,
3528 );
3529 }
3530 #[cfg(target_arch = "wasm32")]
3531 {
3532 let segs_f = segs.max(1);
3533 let step = (a1 - a0) / segs_f as f32;
3534 let mut px = cx + r * a0.cos();
3535 let mut py = cy + r * a0.sin();
3536 let mut i = 1u32;
3537 while i <= segs_f {
3538 let a = a0 + step * i as f32;
3539 let nx = cx + r * a.cos();
3540 let ny = cy + r * a.sin();
3541 gfx.depth_queue.push_line(0.0, color, px, py, nx, ny);
3542 px = nx;
3543 py = ny;
3544 i += 1;
3545 }
3546 }
3547 return Ok(Value::Unit);
3548 },
3549
3550 "set_alpha" | "ตั้งความโปร่งใส" | "设透明" | "アルファ設定" | "투명도설정" | "تنظیم_شفافیت" | "عيّن_الشفافية" | "קבע_שקיפות" | "شفافیت_مقرر_کرو" | "définir_alpha" | "alpha_setzen" | "задать_альфа" =>
3556 {
3557 let a = self.arg_num(&args, 0, 1.0)? as f32;
3558 let mut gfx = self.gfx.borrow_mut();
3559 gfx.alpha = a.clamp(0.0, 1.0);
3560 let (m, al) = (gfx.blend, gfx.alpha);
3561 gfx.depth_queue.set_state(m, al); return Ok(Value::Unit);
3563 },
3564
3565 "mesh_hue" | "หมุนสีเมช" | "فام_مش" | "صبغة_الشبكة" | "גוון_רשת" | "میش_ہیو" =>
3568 {
3569 let h = self.arg_num(&args, 0, 0.0)? as f32;
3570 let g = self.arg_num(&args, 1, 1.0)? as f32;
3571 let mut gfx = self.gfx.borrow_mut();
3572 gfx.mesh_hue = h;
3573 gfx.mesh_hue_gain = g.max(0.0);
3574 return Ok(Value::Unit);
3575 },
3576
3577 "set_frame_blur" | "frame_blur" | "เบลอเฟรม" | "تنظیم_تاری_فریم" | "عيّن_ضبابية_الإطار" | "קבע_טשטוש_פריים" | "فریم_بلر_مقرر_کرو" =>
3580 {
3581 let a = self.arg_num(&args, 0, 0.0)? as f32;
3582 let mut gfx = self.gfx.borrow_mut();
3583 gfx.frame_blur = a.clamp(0.0, 0.95);
3584 if gfx.frame_blur <= 0.0 {
3585 gfx.prev_frame = Vec::new();
3586 }
3587 return Ok(Value::Unit);
3588 },
3589
3590 "set_line_hue_cycle" | "ตั้งวนสีเส้น" | "تنظیم_چرخه_فام_خط" | "عيّن_دورة_صبغة_الخط" | "קבע_מחזור_גוון_קו" | "لائن_ہیو_سائیکل_مقرر_کرو" => {
3594 let rate = self.arg_num(&args, 0, 0.0)?;
3595 crate::runtime::set_line_hue_rate(rate);
3596 return Ok(Value::Unit);
3597 },
3598
3599 "set_color_space" | "ปริภูมิสี" | "色彩空间" | "色空間" | "색공간" | "تنظیم_فضای_رنگ" | "عيّن_فضاء_اللون" | "קבע_מרחב_צבע" | "کلر_اسپیس_مقرر_کرو" | "définir_espace_couleur" | "farbraum_setzen" | "задать_цветовое_пространство" =>
3603 {
3604 let m = self.arg_num(&args, 0, 0.0)? as i64;
3605 self.gfx.borrow_mut().linear_blend = m != 0;
3606 return Ok(Value::Unit);
3607 },
3608
3609 "set_gradient_space" | "ปริภูมิไล่สี" | "渐变空间" | "グラデ空間" | "그라데이션공간" | "تنظیم_فضای_گرادیان" | "عيّن_فضاء_التدرج" | "קבע_מרחב_גרדיאנט" | "گریڈینٹ_اسپیس_مقرر_کرو" | "définir_espace_dégradé" | "verlaufsraum_setzen" | "задать_пространство_градиента" =>
3612 {
3613 let m = self.arg_num(&args, 0, 1.0)? as i64;
3614 self.gfx.borrow_mut().grad_oklab = m != 0;
3615 return Ok(Value::Unit);
3616 },
3617
3618 "mix_color" | "ผสมสี" | "混合颜色" | "色混合" | "색혼합" | "ترکیب_رنگ" | "امزج_اللون" | "ערבב_צבע" | "رنگ_ملاؤ" | "mélanger_couleur" | "farbe_mischen" | "смешать_цвет" => {
3622 let c0 = rgb(
3623 self.arg_num(&args, 0, 0.0)?,
3624 self.arg_num(&args, 1, 0.0)?,
3625 self.arg_num(&args, 2, 0.0)?,
3626 );
3627 let c1 = rgb(
3628 self.arg_num(&args, 3, 255.0)?,
3629 self.arg_num(&args, 4, 255.0)?,
3630 self.arg_num(&args, 5, 255.0)?,
3631 );
3632 let t = self.arg_num(&args, 6, 0.5)? as f32;
3633 self.gfx.borrow_mut().color = crate::gfx::color::mix_oklab(c0, c1, t);
3634 return Ok(Value::Unit);
3635 },
3636
3637 "set_depth_test" | "ทดสอบความลึก" | "深度测试" | "深度テスト" | "깊이테스트" | "تنظیم_آزمون_عمق" | "عيّن_اختبار_العمق" | "קבע_בדיקת_עומק" | "ڈیپتھ_ٹیسٹ_مقرر_کرو" | "définir_test_profondeur" | "tiefentest_setzen" | "задать_тест_глубины" =>
3641 {
3642 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3643 self.gfx.borrow_mut().depth_test = on;
3644 return Ok(Value::Unit);
3645 },
3646
3647 "set_flat_shade" | "ตั้งแฟลตเชด" | "平面着色" | "フラット着色" | "평면음영" | "تنظیم_سایهپردازی_تخت" | "عيّن_تظليلا_مسطحا" | "קבע_הצללה_שטוחה" | "فلیٹ_شیڈ_مقرر_کرو" =>
3650 {
3651 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
3652 self.gfx.borrow_mut().flat_shade = on;
3653 return Ok(Value::Unit);
3654 },
3655
3656 "set_normal_override" =>
3659 {
3660 let x = self.arg_num(&args, 0, 0.0)? as f32;
3661 let y = self.arg_num(&args, 1, -1.0)? as f32;
3662 let z = self.arg_num(&args, 2, 0.0)? as f32;
3663 self.gfx.borrow_mut().normal_override = Some([x, y, z]);
3664 return Ok(Value::Unit);
3665 },
3666
3667 "reset_normal_override" =>
3668 {
3669 self.gfx.borrow_mut().normal_override = None;
3670 return Ok(Value::Unit);
3671 },
3672
3673 "clear_depth" | "ล้างความลึก" | "清深度" | "深度クリア" | "깊이지우기" | "پاککردن_عمق" | "امسح_العمق" | "נקה_עומק" | "گہرائی_صاف_کرو" =>
3677 {
3678 self.gfx.borrow_mut().zbuf_needs_clear = true;
3679 return Ok(Value::Unit);
3680 },
3681
3682 "depth_blur" | "เบลอความลึก" | "dof" | "depth_of_field" | "景深" | "تاری_عمق" | "ضبابية_العمق" | "טשטוש_עומק" | "ڈیپتھ_بلر" =>
3688 {
3689 let focus = self.arg_num(&args, 0, 30.0)? as f32;
3690 let range = self.arg_num(&args, 1, 60.0)? as f32;
3691 let radius = self.arg_num(&args, 2, 3.0)?.max(0.0) as usize;
3692 let oil = self.arg_num(&args, 3, 0.0)? as f32;
3695 let mut gfx = self.gfx.borrow_mut();
3696 let w = gfx.width;
3697 let h = gfx.height;
3698 if gfx.depth_buf.len() == w * h {
3699 let g = &mut *gfx;
3700 crate::gfx::raster::depth_of_field(
3701 &mut g.buffer,
3702 &g.depth_buf,
3703 w,
3704 h,
3705 focus,
3706 range,
3707 radius,
3708 oil,
3709 );
3710 }
3711 return Ok(Value::Unit);
3712 },
3713
3714 "light_pool" | "แอ่งแสง" | "光池" | "ライトプール" | "빛웅덩이" | "برکه_نور" | "بركة_ضوء" | "בריכת_אור" | "روشنی_تالاب" | "bassin_lumière" | "lichtpfütze" | "лужа_света" =>
3720 {
3721 let x = self.arg_num(&args, 0, 0.0)? as f32;
3722 let y = self.arg_num(&args, 1, 0.0)? as f32;
3723 let z = self.arg_num(&args, 2, 0.0)? as f32;
3724 let radius = self.arg_num(&args, 3, 20.0)? as f32;
3725 let r = self.arg_num(&args, 4, 255.0)? as f32 / 255.0;
3726 let g = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3727 let b = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3728 let inten = self.arg_num(&args, 7, 1.0)? as f32;
3729 self.gfx
3730 .borrow_mut()
3731 .emit_light_pool(x, y, z, radius, [r, g, b], inten);
3732 return Ok(Value::Unit);
3733 },
3734
3735 "light_beam" | "ลำแสงไฟ" | "光柱" | "ライトビーム" | "빛기둥" | "پرتو_نور" | "شعاع_ضوء" | "קרן_אור" | "روشنی_شعاع" | "faisceau_lumière" | "lichtstrahl" | "луч_света" =>
3740 {
3741 let x = self.arg_num(&args, 0, 0.0)? as f32;
3742 let y = self.arg_num(&args, 1, 0.0)? as f32;
3743 let z = self.arg_num(&args, 2, 0.0)? as f32;
3744 let fy = self.arg_num(&args, 3, 0.0)? as f32;
3745 let radius = self.arg_num(&args, 4, 14.0)? as f32;
3746 let r = self.arg_num(&args, 5, 255.0)? as f32 / 255.0;
3747 let g = self.arg_num(&args, 6, 255.0)? as f32 / 255.0;
3748 let b = self.arg_num(&args, 7, 255.0)? as f32 / 255.0;
3749 let inten = self.arg_num(&args, 8, 1.0)? as f32;
3750 self.gfx
3751 .borrow_mut()
3752 .emit_light_beam(x, y, z, fy, radius, [r, g, b], inten);
3753 return Ok(Value::Unit);
3754 },
3755
3756 "grad_triangle" | "สามเหลี่ยมไล่สี" | "渐变三角" | "グラデ三角" | "그라데삼각" | "مثلث_گرادیان" | "مثلث_متدرج" | "משולש_גרדיאנט" | "گریڈینٹ_مثلث" | "triangle_dégradé" | "dreieck_verlauf" | "градиент_треугольник" =>
3760 {
3761 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3762 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3763 let c0 = rgb(
3764 self.arg_num(&args, 2, 255.0)?,
3765 self.arg_num(&args, 3, 255.0)?,
3766 self.arg_num(&args, 4, 255.0)?,
3767 );
3768 let x1 = self.arg_num(&args, 5, 0.0)? as f32;
3769 let y1 = self.arg_num(&args, 6, 0.0)? as f32;
3770 let c1 = rgb(
3771 self.arg_num(&args, 7, 255.0)?,
3772 self.arg_num(&args, 8, 255.0)?,
3773 self.arg_num(&args, 9, 255.0)?,
3774 );
3775 let x2 = self.arg_num(&args, 10, 0.0)? as f32;
3776 let y2 = self.arg_num(&args, 11, 0.0)? as f32;
3777 let c2 = rgb(
3778 self.arg_num(&args, 12, 255.0)?,
3779 self.arg_num(&args, 13, 255.0)?,
3780 self.arg_num(&args, 14, 255.0)?,
3781 );
3782 let mut gfx = self.gfx.borrow_mut();
3783 let (w, h, alpha, mode, lin, ok) = (
3784 gfx.width,
3785 gfx.height,
3786 gfx.alpha,
3787 gfx.blend,
3788 gfx.linear_blend,
3789 gfx.grad_oklab,
3790 );
3791 crate::gfx::raster::fill_triangle_grad(
3792 &mut gfx.buffer,
3793 w,
3794 h,
3795 alpha,
3796 mode,
3797 lin,
3798 ok,
3799 x0,
3800 y0,
3801 c0,
3802 x1,
3803 y1,
3804 c1,
3805 x2,
3806 y2,
3807 c2,
3808 );
3809 return Ok(Value::Unit);
3810 },
3811
3812 "grad_rect" | "สี่เหลี่ยมไล่สี" | "渐变矩形" | "グラデ矩形" | "그라데사각" | "مستطیل_گرادیان" | "مستطيل_متدرج" | "מלבן_גרדיאנט" | "گریڈینٹ_مستطیل" | "rectangle_dégradé" | "rechteck_verlauf" | "градиент_прямоугольник" =>
3815 {
3816 let x = self.arg_num(&args, 0, 0.0)? as f32;
3817 let y = self.arg_num(&args, 1, 0.0)? as f32;
3818 let rw = self.arg_num(&args, 2, 0.0)? as f32;
3819 let rh = self.arg_num(&args, 3, 0.0)? as f32;
3820 let c0 = rgb(
3821 self.arg_num(&args, 4, 255.0)?,
3822 self.arg_num(&args, 5, 255.0)?,
3823 self.arg_num(&args, 6, 255.0)?,
3824 );
3825 let c1 = rgb(
3826 self.arg_num(&args, 7, 0.0)?,
3827 self.arg_num(&args, 8, 0.0)?,
3828 self.arg_num(&args, 9, 0.0)?,
3829 );
3830 let dir = self.arg_num(&args, 10, 1.0)? as u8;
3831 let mut gfx = self.gfx.borrow_mut();
3832 let (w, h, alpha, mode, lin, ok) = (
3833 gfx.width,
3834 gfx.height,
3835 gfx.alpha,
3836 gfx.blend,
3837 gfx.linear_blend,
3838 gfx.grad_oklab,
3839 );
3840 crate::gfx::raster::fill_rect_grad(
3841 &mut gfx.buffer,
3842 w,
3843 h,
3844 alpha,
3845 mode,
3846 lin,
3847 ok,
3848 x,
3849 y,
3850 rw,
3851 rh,
3852 c0,
3853 c1,
3854 dir,
3855 );
3856 return Ok(Value::Unit);
3857 },
3858
3859 "shadow_blob" | "เงาวงรี" | "阴影斑" | "影ブロブ" | "그림자블롭" | "لکه_سایه" | "بقعة_ظل" | "כתם_צל" | "سایہ_دھبہ" | "tache_ombre" | "schattenklecks" | "пятно_тени" =>
3863 {
3864 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3865 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3866 let rx = self.arg_num(&args, 2, 16.0)? as f32;
3867 let ry = self.arg_num(&args, 3, 8.0)? as f32;
3868 let a = self.arg_num(&args, 4, 0.5)? as f32;
3869 let mut gfx = self.gfx.borrow_mut();
3870 let (w, h, color, soft, mode, lin) = (
3871 gfx.width,
3872 gfx.height,
3873 gfx.color,
3874 gfx.shadow.soft,
3875 gfx.blend,
3876 gfx.linear_blend,
3877 );
3878 crate::gfx::raster::fill_disc_soft(
3879 &mut gfx.buffer,
3880 w,
3881 h,
3882 cx,
3883 cy,
3884 rx,
3885 ry,
3886 color,
3887 a,
3888 soft,
3889 mode,
3890 lin,
3891 );
3892 return Ok(Value::Unit);
3893 },
3894
3895 "cast_shadow" | "ทอดเงา" | "投射阴影" | "影を落とす" | "그림자드리우기" | "افکندن_سایه" | "ألقِ_ظلا" | "הטל_צל" | "سایہ_ڈالو" | "projeter_ombre" | "schatten_werfen" | "отбросить_тень" =>
3900 {
3901 let cx = self.arg_num(&args, 0, 0.0)? as f32;
3902 let cy = self.arg_num(&args, 1, 0.0)? as f32;
3903 let height = (self.arg_num(&args, 2, 0.0)? as f32).max(0.0);
3904 let mut gfx = self.gfx.borrow_mut();
3905 let sp = gfx.shadow;
3906 let radius = (sp.base + sp.grow * height).max(0.5);
3907 let alpha = (sp.alpha - sp.fade * height).clamp(0.04, 1.0);
3908 let soft = (sp.soft + height * 0.004).clamp(0.0, 0.95);
3909 let (w, h, color, mode, lin) = (
3910 gfx.width,
3911 gfx.height,
3912 gfx.color,
3913 gfx.blend,
3914 gfx.linear_blend,
3915 );
3916 crate::gfx::raster::fill_disc_soft(
3917 &mut gfx.buffer,
3918 w,
3919 h,
3920 cx,
3921 cy,
3922 radius,
3923 radius * 0.62,
3924 color,
3925 alpha,
3926 soft,
3927 mode,
3928 lin,
3929 );
3930 return Ok(Value::Unit);
3931 },
3932
3933 "shadow_params" | "ตั้งค่าเงา" | "阴影参数" | "影設定" | "그림자설정" | "پارامترهای_سایه" | "معاملات_الظل" | "פרמטרי_צל" | "سایہ_پیرامیٹرز" | "paramètres_ombre" | "schattenparameter" | "параметры_тени" =>
3936 {
3937 let cur = self.gfx.borrow().shadow;
3938 let base = self.arg_num(&args, 0, cur.base as f64)? as f32;
3939 let grow = self.arg_num(&args, 1, cur.grow as f64)? as f32;
3940 let alpha = self.arg_num(&args, 2, cur.alpha as f64)? as f32;
3941 let fade = self.arg_num(&args, 3, cur.fade as f64)? as f32;
3942 let soft = self.arg_num(&args, 4, cur.soft as f64)? as f32;
3943 self.gfx.borrow_mut().shadow =
3944 crate::gfx::ShadowParams { base, grow, alpha, fade, soft };
3945 return Ok(Value::Unit);
3946 },
3947
3948 "depth_triangle" | "สามเหลี่ยมเรียงลึก" | "深度三角" | "深度三角形" | "깊이삼각" | "مثلث_عمق" | "مثلث_العمق" | "משולש_עומק" | "گہرائی_مثلث" | "triangle_profondeur" | "tiefendreieck" | "глубина_треугольник" =>
3953 {
3954 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3955 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3956 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3957 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3958 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
3959 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
3960 let z = self.arg_num(&args, 6, 0.0)? as f32;
3961 let mut gfx = self.gfx.borrow_mut();
3962 let color = gfx.color;
3963 gfx.depth_queue
3964 .push_triangle(z, color, x0, y0, x1, y1, x2, y2);
3965 return Ok(Value::Unit);
3966 },
3967
3968 "depth_line" | "เส้นเรียงลึก" | "深度线" | "深度線" | "깊이선" | "خط_عمق" | "خط_العمق" | "קו_עומק" | "گہرائی_لکیر" | "ligne_profondeur" | "tiefenlinie" | "глубина_линия" =>
3971 {
3972 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
3973 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
3974 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
3975 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
3976 let z = self.arg_num(&args, 4, 0.0)? as f32;
3977 let mut gfx = self.gfx.borrow_mut();
3978 let color = gfx.color;
3979 gfx.depth_queue.push_line(z, color, x0, y0, x1, y1);
3980 return Ok(Value::Unit);
3981 },
3982
3983 "เปิดหน้าต่าง" | "open_window" | "gfx_window" | "开窗" | "ウィンドウ開く" | "창열기" | "باز_کردن_پنجره" | "افتح_نافذة" | "פתח_חלון" | "ونڈو_کھولو" =>
3990 {
3991 let w = self.arg_num(&args, 0, 800.0)? as usize;
3992 let h = self.arg_num(&args, 1, 600.0)? as usize;
3993 #[cfg(not(target_arch = "wasm32"))]
3994 {
3995 let title = args
3996 .get(2)
3997 .map(|v| v.to_string())
3998 .unwrap_or_else(|| "Ling".into());
3999 let mut gfx = self.gfx.borrow_mut();
4000 let mut win = minifb::Window::new(
4001 &title,
4002 w,
4003 h,
4004 minifb::WindowOptions {
4005 resize: false,
4006 scale: minifb::Scale::X1,
4007 ..Default::default()
4008 },
4009 )
4010 .map_err(|e| EvalErr::from(format!("cannot open window: {e}")))?;
4011 apply_frame_pacing(&mut win, gfx.vsync);
4012 gfx.buffer = vec![0u32; w * h];
4013 gfx.width = w;
4014 gfx.height = h;
4015 gfx.window = Some(win);
4016 gfx.topmost_window = false;
4017 gfx.sync_projection();
4018 hide_console_window();
4019 }
4020 #[cfg(target_arch = "wasm32")]
4021 {
4022 let mut gfx = self.gfx.borrow_mut();
4023 gfx.width = w;
4024 gfx.height = h;
4025 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4027 crate::gfx::webgl::resize(w as u32, h as u32);
4028 }
4029 return Ok(Value::Unit);
4030 },
4031
4032 "เติม" | "fill" | "gfx_fill" | "clear" | "填" | "塗り潰し" | "채우기" | "清"
4034 | "消去" | "지우기" | "پر_کن" | "املأ" | "מלא" | "بھرو" => {
4035 let r = self.arg_num(&args, 0, 0.0)? as u32;
4036 let g = self.arg_num(&args, 1, 0.0)? as u32;
4037 let b = self.arg_num(&args, 2, 0.0)? as u32;
4038 #[cfg(not(target_arch = "wasm32"))]
4039 {
4040 let c = (r << 16) | (g << 8) | b;
4041 let mut gfx = self.gfx.borrow_mut();
4042 gfx.buffer.fill(c);
4043 gfx.zbuf_needs_clear = true; gfx.edge_set.clear(); }
4046 #[cfg(target_arch = "wasm32")]
4047 {
4048 let mut gfx = self.gfx.borrow_mut();
4049 gfx.fill_r = r as f32 / 255.0;
4050 gfx.fill_g = g as f32 / 255.0;
4051 gfx.fill_b = b as f32 / 255.0;
4052 let c = (r << 16) | (g << 8) | b;
4053 gfx.buffer.fill(c);
4054 gfx.zbuf_needs_clear = true;
4055 gfx.edge_set.clear();
4056 }
4057 return Ok(Value::Unit);
4058 },
4059
4060 "set_color_hsl" | "颜色HSL" | "色相" | "HSL色" | "HSL색설정" | "สีHSLวาด" | "تنظیم_رنگ_HSL" | "عيّن_اللون_HSL" | "קבע_צבע_HSL" | "HSL_رنگ_مقرر_کرو" | "définir_couleur_hsl" | "farbe_hsl_setzen" | "задать_цвет_hsl" =>
4063 {
4064 let h = self.arg_num(&args, 0, 0.0)?;
4065 let s = self.arg_num(&args, 1, 70.0)?;
4066 let l = self.arg_num(&args, 2, 50.0)?;
4067 let hex = hsl_to_hex(h, s, l);
4068 let r = u32::from_str_radix(&hex[1..3], 16).unwrap_or(255);
4069 let g = u32::from_str_radix(&hex[3..5], 16).unwrap_or(255);
4070 let b = u32::from_str_radix(&hex[5..7], 16).unwrap_or(255);
4071 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4072 return Ok(Value::Unit);
4073 },
4074
4075 "สีดินสอ" | "set_color" | "gfx_color" | "color" | "设色" | "色設定" | "색설정" | "تنظیم_رنگ" | "عيّن_اللون" | "קבע_צבע" | "رنگ_مقرر_کرو" =>
4077 {
4078 let r = self.arg_num(&args, 0, 255.0)? as u32;
4079 let g = self.arg_num(&args, 1, 255.0)? as u32;
4080 let b = self.arg_num(&args, 2, 255.0)? as u32;
4081 self.gfx.borrow_mut().color = (r << 16) | (g << 8) | b;
4082 return Ok(Value::Unit);
4083 },
4084
4085 "วาดสามเหลี่ยม"
4087 | "draw_triangle"
4088 | "gfx_triangle"
4089 | "triangle"
4090 | "画三角"
4091 | "三角形描画"
4092 | "삼각형그리기" | "رسم_مثلث" | "ارسم_مثلثا" | "צייר_משולש" | "مثلث_کھینچو" => {
4093 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4094 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4095 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4096 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4097 let x2 = self.arg_num(&args, 4, 0.0)? as f32;
4098 let y2 = self.arg_num(&args, 5, 0.0)? as f32;
4099 let mut gfx = self.gfx.borrow_mut();
4100 let color = gfx.color;
4101 #[cfg(not(target_arch = "wasm32"))]
4102 {
4103 let w = gfx.width;
4104 let h = gfx.height;
4105 fill_triangle(&mut gfx.buffer, w, h, color, x0, y0, x1, y1, x2, y2);
4106 }
4107 #[cfg(target_arch = "wasm32")]
4108 gfx.depth_queue
4109 .push_triangle(0.0, color, x0, y0, x1, y1, x2, y2);
4110 return Ok(Value::Unit);
4111 },
4112
4113 "วาดเส้น" | "draw_line" | "gfx_line" | "line" | "画线" | "線描く" | "선그리기" | "رسم_خط" | "ارسم_خط" | "צייר_קו" | "لکیر_کھینچو" =>
4115 {
4116 let x0 = self.arg_num(&args, 0, 0.0)? as f32;
4117 let y0 = self.arg_num(&args, 1, 0.0)? as f32;
4118 let x1 = self.arg_num(&args, 2, 0.0)? as f32;
4119 let y1 = self.arg_num(&args, 3, 0.0)? as f32;
4120 let mut gfx = self.gfx.borrow_mut();
4121 let color = gfx.color;
4122 #[cfg(not(target_arch = "wasm32"))]
4123 {
4124 let w = gfx.width;
4125 let h = gfx.height;
4126 let aa = gfx.antialias;
4127 let add = gfx.blend == 1;
4128 if aa {
4129 crate::gfx::raster::draw_line_aa(
4130 &mut gfx.buffer,
4131 w,
4132 h,
4133 color,
4134 add,
4135 x0,
4136 y0,
4137 x1,
4138 y1,
4139 );
4140 } else {
4141 draw_line(&mut gfx.buffer, w, h, color, x0, y0, x1, y1);
4142 }
4143 }
4144 #[cfg(target_arch = "wasm32")]
4145 gfx.depth_queue.push_line(0.0, color, x0, y0, x1, y1);
4146 return Ok(Value::Unit);
4147 },
4148
4149 "วาดจุด" | "draw_pixel" | "gfx_pixel" | "pixel" | "画点" | "点描く" | "점그리기" | "رسم_نقطه" | "ارسم_نقطة" | "צייר_פיקסל" | "پکسل_کھینچو" =>
4151 {
4152 let px = self.arg_num(&args, 0, 0.0)? as i32;
4153 let py = self.arg_num(&args, 1, 0.0)? as i32;
4154 #[cfg(not(target_arch = "wasm32"))]
4155 {
4156 let mut gfx = self.gfx.borrow_mut();
4157 let color = gfx.color;
4158 let w = gfx.width;
4159 let h = gfx.height;
4160 if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
4161 gfx.buffer[py as usize * w + px as usize] = color;
4162 }
4163 }
4164 #[cfg(target_arch = "wasm32")]
4165 {
4166 let mut gfx = self.gfx.borrow_mut();
4168 let color = gfx.color;
4169 let x = px as f32;
4170 let y = py as f32;
4171 gfx.depth_queue
4172 .push_triangle(0.0, color, x, y, x + 1.0, y, x + 1.0, y + 1.0);
4173 gfx.depth_queue
4174 .push_triangle(0.0, color, x, y, x + 1.0, y + 1.0, x, y + 1.0);
4175 }
4176 return Ok(Value::Unit);
4177 },
4178
4179 "แสดงผล" | "present" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시" | "نمایش" | "اعرض" | "הצג" | "دکھاؤ" =>
4181 {
4182 #[cfg(not(target_arch = "wasm32"))]
4195 let pre_update_mouse_down = self
4196 .gfx
4197 .borrow()
4198 .window
4199 .as_ref()
4200 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
4201 .unwrap_or(false);
4202 #[cfg(not(target_arch = "wasm32"))]
4203 {
4204 ling_fps_tick();
4205 ling_phase_frame();
4206 {
4208 let mut gfx = self.gfx.borrow_mut();
4209 if !gfx.depth_queue.is_empty() {
4210 let w = gfx.width;
4211 let h = gfx.height;
4212 let dt = gfx.depth_test;
4213 let reset_z = gfx.zbuf_needs_clear;
4214 let (bm, ba) = (gfx.blend, gfx.alpha);
4215 let aa = gfx.antialias;
4216 let queue = std::mem::take(&mut gfx.depth_queue);
4217 {
4218 let g = &mut *gfx;
4219 let z = if dt { Some(&mut g.depth_buf) } else { None };
4220 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4221 }
4222 gfx.depth_queue.set_state(bm, ba);
4223 gfx.zbuf_needs_clear = false;
4224 }
4225 let _t = std::time::Instant::now();
4226 if !gfx.post_done {
4227 gfx.toon_post_process();
4228 }
4229 gfx.post_done = false;
4230 ling_phase_add(phase::TOON, _t.elapsed().as_nanos());
4231 let w = gfx.width;
4232 let h = gfx.height;
4233 let g = &mut *gfx;
4234 if g.frame_blur > 0.0 {
4235 let a = (g.frame_blur.clamp(0.0, 0.995) * 256.0) as u32;
4240 if g.prev_frame.len() != g.buffer.len() {
4241 g.prev_frame = g.buffer.clone();
4242 }
4243 for (dst, prev) in g.buffer.iter_mut().zip(g.prev_frame.iter_mut()) {
4244 let c = *dst;
4245 let pv = *prev;
4246 let pr = (((pv >> 16) & 0xFF) * a) >> 8;
4247 let pg = (((pv >> 8) & 0xFF) * a) >> 8;
4248 let pb = ((pv & 0xFF) * a) >> 8;
4249 let cr = (c >> 16) & 0xFF;
4250 let cg = (c >> 8) & 0xFF;
4251 let cb = c & 0xFF;
4252 let outp = (cr.max(pr) << 16) | (cg.max(pg) << 8) | cb.max(pb);
4253 *dst = outp;
4254 *prev = outp;
4255 }
4256 }
4257 if let Some(win) = g.window.as_mut() {
4258 let _b = std::time::Instant::now();
4259 win.update_with_buffer(&g.buffer, w, h)
4260 .map_err(|e| EvalErr::from(format!("present error: {e}")))?;
4261 ling_phase_add(phase::BLIT, _b.elapsed().as_nanos());
4262 }
4263 }
4264 let mouse_pos = {
4266 let gfx = self.gfx.borrow();
4267 gfx.window
4268 .as_ref()
4269 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
4270 };
4271 let mut gfx = self.gfx.borrow_mut();
4272 if gfx.mouse_captured {
4273 let w = gfx.width as f32;
4274 let h = gfx.height as f32;
4275 if let Some((mx, my)) = mouse_pos {
4276 if gfx.last_mx.is_nan() {
4277 gfx.mouse_dx = 0.0;
4278 gfx.mouse_dy = 0.0;
4279 gfx.last_mx = mx;
4280 gfx.last_my = my;
4281 } else {
4282 gfx.mouse_dx = mx - gfx.last_mx;
4283 gfx.mouse_dy = my - gfx.last_my;
4284 let margin = 6.0;
4287 let (mut nx, mut ny, mut warp) = (mx, my, false);
4288 if mx < margin {
4289 nx = w - margin - 2.0;
4290 warp = true;
4291 } else if mx > w - margin {
4292 nx = margin + 2.0;
4293 warp = true;
4294 }
4295 if my < margin {
4296 ny = h - margin - 2.0;
4297 warp = true;
4298 } else if my > h - margin {
4299 ny = margin + 2.0;
4300 warp = true;
4301 }
4302 if warp {
4303 #[cfg(windows)]
4304 unsafe {
4305 #[repr(C)]
4306 struct RECT {
4307 left: i32,
4308 top: i32,
4309 right: i32,
4310 bottom: i32,
4311 }
4312 extern "system" {
4313 fn GetForegroundWindow() -> isize;
4314 fn GetWindowRect(hwnd: isize, lpRect: *mut RECT)
4315 -> i32;
4316 fn SetCursorPos(x: i32, y: i32) -> i32;
4317 }
4318 let hwnd = GetForegroundWindow();
4319 let mut rect =
4320 RECT { left: 0, top: 0, right: 0, bottom: 0 };
4321 if GetWindowRect(hwnd, &mut rect) != 0 {
4322 SetCursorPos(
4323 rect.left + nx as i32,
4324 rect.top + ny as i32,
4325 );
4326 }
4327 }
4328 gfx.last_mx = nx;
4329 gfx.last_my = ny;
4330 } else {
4331 gfx.last_mx = mx;
4332 gfx.last_my = my;
4333 }
4334 }
4335 } else {
4336 gfx.mouse_dx = 0.0;
4337 gfx.mouse_dy = 0.0;
4338 }
4339 } else if let Some((mx, my)) = mouse_pos {
4340 if gfx.last_mx.is_nan() {
4341 gfx.mouse_dx = 0.0;
4342 gfx.mouse_dy = 0.0;
4343 } else {
4344 gfx.mouse_dx = mx - gfx.last_mx;
4345 gfx.mouse_dy = my - gfx.last_my;
4346 }
4347 gfx.last_mx = mx;
4348 gfx.last_my = my;
4349 } else {
4350 gfx.mouse_dx = 0.0;
4351 gfx.mouse_dy = 0.0;
4352 }
4353
4354 let is_active = gfx.window.as_mut().map(|w| w.is_active()).unwrap_or(true);
4362 if is_active && !gfx.was_active {
4363 gfx.focus_grace_frames = 5;
4364 #[cfg(windows)]
4367 if gfx.topmost_window {
4368 if let Some(w) = gfx.window.as_ref() {
4369 set_window_topmost(w.get_window_handle() as isize, true);
4370 }
4371 }
4372 } else if !is_active && gfx.was_active {
4373 #[cfg(windows)]
4379 if gfx.topmost_window {
4380 if let Some(w) = gfx.window.as_ref() {
4381 set_window_topmost(w.get_window_handle() as isize, false);
4382 }
4383 }
4384 }
4385 gfx.was_active = is_active;
4386 if gfx.focus_grace_frames > 0 {
4387 gfx.focus_grace_frames -= 1;
4388 }
4389 }
4390 #[cfg(target_arch = "wasm32")]
4391 {
4392 {
4393 let mut gfx = self.gfx.borrow_mut();
4397 let w = gfx.width;
4398 let h = gfx.height;
4399 if gfx.buffer.len() != w * h {
4400 gfx.buffer.resize(w * h, 0);
4401 }
4402 if !gfx.depth_queue.is_empty() {
4403 let dt = gfx.depth_test;
4404 let reset_z = gfx.zbuf_needs_clear;
4405 let aa = gfx.antialias;
4406 let queue = std::mem::take(&mut gfx.depth_queue);
4407 {
4408 let g = &mut *gfx;
4409 let z = if dt { Some(&mut g.depth_buf) } else { None };
4410 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
4411 }
4412 gfx.zbuf_needs_clear = false;
4413 }
4414 if !gfx.post_done {
4415 gfx.toon_post_process();
4416 }
4417 gfx.post_done = false;
4418 crate::gfx::webgl::blit_rgb(&gfx.buffer, w, h);
4419 }
4420 self.wasm_pace_frame();
4421 }
4422 #[cfg(not(target_arch = "wasm32"))]
4426 {
4427 self.mouse_was_down = pre_update_mouse_down;
4428 }
4429 self.frame_num += 1;
4431 return Ok(Value::Unit);
4432 },
4433
4434 "เปิดหน้าต่างเต็มจอ"
4436 | "open_fullscreen"
4437 | "fullscreen"
4438 | "全屏"
4439 | "全画面"
4440 | "전체화면" | "باز_کردن_تمامصفحه" | "افتح_ملء_الشاشة" | "פתח_מסך_מלא" | "فل_سکرین_کھولو" => {
4441 #[cfg(target_arch = "wasm32")]
4444 let (default_w, default_h) = {
4445 let (cw, ch) = crate::gfx::webgl::canvas_size();
4446 (cw as f64, ch as f64)
4447 };
4448 #[cfg(all(not(target_arch = "wasm32"), windows))]
4450 let (default_w, default_h) = unsafe {
4451 extern "system" {
4452 fn GetSystemMetrics(nIndex: i32) -> i32;
4453 }
4454 (GetSystemMetrics(0) as f64, GetSystemMetrics(1) as f64)
4455 };
4456 #[cfg(all(not(target_arch = "wasm32"), not(windows)))]
4457 let (default_w, default_h) = native_screen_size();
4458
4459 let w = args
4460 .get(1)
4461 .map(|v| self.to_number(v).unwrap_or(default_w) as usize)
4462 .unwrap_or(default_w as usize);
4463 let h = args
4464 .get(2)
4465 .map(|v| self.to_number(v).unwrap_or(default_h) as usize)
4466 .unwrap_or(default_h as usize);
4467 #[cfg(not(target_arch = "wasm32"))]
4468 {
4469 let title = args
4470 .first()
4471 .map(|v| v.to_string())
4472 .unwrap_or_else(|| "Ling".into());
4473 let mut gfx = self.gfx.borrow_mut();
4474 let mut win = minifb::Window::new(
4475 &title,
4476 w,
4477 h,
4478 minifb::WindowOptions {
4479 borderless: true,
4480 title: false,
4481 resize: false,
4482 topmost: true,
4483 scale: minifb::Scale::X1,
4484 ..Default::default()
4485 },
4486 )
4487 .map_err(|e| EvalErr::from(format!("cannot open fullscreen: {e}")))?;
4488 apply_frame_pacing(&mut win, gfx.vsync);
4489 #[cfg(windows)]
4491 let hwnd = win.get_window_handle() as isize;
4492 gfx.buffer = vec![0u32; w * h];
4493 gfx.width = w;
4494 gfx.height = h;
4495 gfx.window = Some(win);
4496 gfx.topmost_window = true;
4497 #[cfg(windows)]
4498 {
4499 gfx.hwnd = hwnd;
4500 }
4501 gfx.sync_projection();
4502 #[cfg(windows)]
4504 make_borderless_fullscreen(hwnd, w as i32, h as i32);
4505 hide_console_window();
4506 #[cfg(windows)]
4513 force_window_focus(hwnd);
4514 }
4515 #[cfg(target_arch = "wasm32")]
4516 {
4517 let mut gfx = self.gfx.borrow_mut();
4518 gfx.width = w;
4519 gfx.height = h;
4520 gfx.buffer.resize(w * h, 0); gfx.sync_projection();
4522 crate::gfx::webgl::resize(w as u32, h as u32);
4523 }
4524 return Ok(Value::Unit);
4525 },
4526
4527 "get_width" | "ความกว้าง" | "宽" | "幅取得" | "너비" | "عرض" | "العرض" | "רוחב" | "چوڑائی" | "obtenir_largeur" | "breite_abrufen" | "получить_ширину" => {
4529 return Ok(Value::Number(self.gfx.borrow().width as f64));
4530 },
4531 "get_height" | "ความสูง" | "高" | "高取得" | "높이" | "ارتفاع" | "الارتفاع" | "גובה" | "اونچائی" | "obtenir_hauteur" | "höhe_abrufen" | "получить_высоту" => {
4532 return Ok(Value::Number(self.gfx.borrow().height as f64));
4533 },
4534
4535 "monitor_width" | "screen_width" | "屏宽" | "画面幅" | "화면너비" | "ความกว้างจอ" | "عرض_مانیتور" | "عرض_الشاشة" | "רוחב_צג" | "مانیٹر_چوڑائی" | "largeur_moniteur" | "monitor_breite" | "ширина_монитора" =>
4538 {
4539 return Ok(Value::Number(monitor_info().0 as f64));
4540 },
4541 "monitor_height" | "screen_height" | "屏高" | "画面高" | "화면높이" | "ความสูงจอ" | "ارتفاع_مانیتور" | "ارتفاع_الشاشة" | "גובה_צג" | "مانیٹر_اونچائی" | "hauteur_moniteur" | "monitor_höhe" | "высота_монитора" =>
4543 {
4544 return Ok(Value::Number(monitor_info().1 as f64));
4545 },
4546 "monitor_refresh"
4548 | "monitor_hz"
4549 | "monitor_fps"
4550 | "refresh_rate"
4551 | "刷新率"
4552 | "リフレッシュレート"
4553 | "주사율"
4554 | "อัตรารีเฟรช" | "نرخ_بروزرسانی_مانیتور" | "معدل_تحديث_الشاشة" | "קצב_רענון_צג" | "مانیٹر_ریفریش_ریٹ" | "fréquence_moniteur" | "monitor_bildwiederholrate" | "частота_монитора" => {
4555 return Ok(Value::Number(monitor_info().2 as f64));
4556 },
4557 "monitor_info" | "screen_info" | "屏幕信息" | "画面情報" | "화면정보" | "ข้อมูลจอ" | "اطلاعات_مانیتور" | "معلومات_الشاشة" | "מידע_צג" | "مانیٹر_معلومات" | "info_moniteur" | "bildschirminfo" | "инфо_монитора" =>
4559 {
4560 let (w, h, hz) = monitor_info();
4561 return Ok(Value::List(Rc::new(vec![
4562 Value::Number(w as f64),
4563 Value::Number(h as f64),
4564 Value::Number(hz as f64),
4565 ])));
4566 },
4567 "set_fps"
4569 | "set_target_fps"
4570 | "target_fps"
4571 | "设帧率"
4572 | "フレームレート設定"
4573 | "프레임설정"
4574 | "ตั้งเฟรมเรต" | "تنظیم_نرخ_فریم" | "عيّن_معدل_الإطارات" | "קבע_קצב_פריימים" | "ایف_پی_ایس_مقرر_کرو" | "définir_fps" | "fps_setzen" | "задать_fps" => {
4575 #[cfg(not(target_arch = "wasm32"))]
4576 {
4577 let fps = self.arg_num(&args, 0, 60.0)?.max(1.0) as usize;
4578 let mut gfx = self.gfx.borrow_mut();
4579 if let Some(win) = gfx.window.as_mut() {
4580 win.set_target_fps(fps);
4581 }
4582 }
4583 #[cfg(target_arch = "wasm32")]
4584 {
4585 self.wasm_target_fps = self.arg_num(&args, 0, 60.0)?.max(1.0);
4586 self.wasm_next_present_ms = 0.0;
4587 }
4588 return Ok(Value::Unit);
4589 },
4590
4591 "set_vsync" | "vsync" | "垂直同步" | "垂直同期" | "수직동기" | "ตั้งวีซิงก์" | "تنظیم_ویسینک" | "عيّن_تزامن_رأسي" | "קבע_וי_סינק" | "وی_سینک_مقرر_کرو" | "définir_vsync" | "vsync_setzen" | "задать_vsync" =>
4595 {
4596 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
4597 #[cfg(not(target_arch = "wasm32"))]
4598 {
4599 let mut gfx = self.gfx.borrow_mut();
4600 gfx.vsync = on;
4601 if let Some(win) = gfx.window.as_mut() {
4602 apply_frame_pacing(win, on);
4603 }
4604 }
4605 #[cfg(target_arch = "wasm32")]
4606 {
4607 self.wasm_target_fps = if on { monitor_info().2 as f64 } else { 240.0 };
4608 self.wasm_next_present_ms = 0.0;
4609 }
4610 return Ok(Value::Unit);
4611 },
4612
4613 "หน้าต่างเปิดอยู่"
4615 | "window_is_open"
4616 | "gfx_is_open"
4617 | "is_open"
4618 | "窗开"
4619 | "開いている"
4620 | "창열림" | "پنجره_باز_است" | "النافذة_مفتوحة" | "החלון_פתוח" | "ونڈو_کھلی_ہے" => {
4621 #[cfg(not(target_arch = "wasm32"))]
4622 {
4623 let gfx = self.gfx.borrow();
4624 if gfx.want_quit {
4625 return Ok(Value::Bool(false));
4626 }
4627 #[cfg(windows)]
4633 let escape_down = if gfx.topmost_window {
4634 window_is_foreground(gfx.hwnd) && os_key_down(0x1B) } else {
4636 gfx.window
4637 .as_ref()
4638 .map(|w| w.is_key_down(minifb::Key::Escape))
4639 .unwrap_or(false)
4640 };
4641 #[cfg(not(windows))]
4642 let escape_down = gfx
4643 .window
4644 .as_ref()
4645 .map(|w| w.is_key_down(minifb::Key::Escape))
4646 .unwrap_or(false);
4647 let open = gfx.window.as_ref().map(|w| w.is_open()).unwrap_or(false)
4648 && !escape_down;
4649 return Ok(Value::Bool(open));
4650 }
4651 #[cfg(target_arch = "wasm32")]
4652 return Ok(Value::Bool(true));
4653 },
4654
4655 "quit" | "exit_game" | "close_window" => {
4658 #[cfg(not(target_arch = "wasm32"))]
4659 {
4660 self.gfx.borrow_mut().want_quit = true;
4661 }
4662 return Ok(Value::Unit);
4663 },
4664
4665 "key_down" | "กดค้าง" | "按键" | "キー押す" | "키누름" | "کلید_فشرده" | "المفتاح_مضغوط" | "מקש_לחוץ" | "بٹن_دبا_ہوا" | "touche_enfoncée" | "taste_gedrückt" | "клавиша_нажата" => {
4667 #[cfg(not(target_arch = "wasm32"))]
4668 {
4669 let name = self.arg_str(&args, 0, "");
4670 let mut gfx = self.gfx.borrow_mut();
4671 #[cfg(windows)]
4679 if gfx.topmost_window {
4680 if !window_is_foreground(gfx.hwnd) {
4681 return Ok(Value::Bool(false));
4682 }
4683 return Ok(Value::Bool(
4684 str_to_vk(&name).map(os_key_down).unwrap_or(false),
4685 ));
4686 }
4687 if gfx.input_suppressed() {
4688 return Ok(Value::Bool(false));
4689 }
4690 let down = gfx
4691 .window
4692 .as_ref()
4693 .and_then(|w| str_to_minifb_key(&name).map(|k| w.is_key_down(k)))
4694 .unwrap_or(false);
4695 return Ok(Value::Bool(down));
4696 }
4697 #[cfg(target_arch = "wasm32")]
4698 {
4699 let name = self.arg_str(&args, 0, "");
4700 return Ok(Value::Bool(crate::gfx::wasm_is_key_down(&name)));
4701 }
4702 },
4703
4704 "key_pressed" | "กดปุ่ม" | "键按" | "キー押した" | "키눌림" | "فشردن_کلید" | "ضغط_المفتاح" | "לחיצת_מקש" | "بٹن_دبانا" | "touche_appuyée" | "taste_getippt" | "клавиша_нажатие" => {
4706 #[cfg(not(target_arch = "wasm32"))]
4707 {
4708 let name = self.arg_str(&args, 0, "");
4709 let pressed = {
4710 let mut gfx = self.gfx.borrow_mut();
4711 #[cfg(windows)]
4712 let topmost = gfx.topmost_window;
4713 #[cfg(not(windows))]
4714 let topmost = false;
4715 if topmost {
4716 #[cfg(windows)]
4717 {
4718 if !window_is_foreground(gfx.hwnd) {
4719 false
4720 } else {
4721 match str_to_vk(&name) {
4722 Some(vk) => {
4723 let idx = (vk as usize) & 0xFF;
4724 let down = os_key_down(vk);
4725 let was = gfx.raw_keys_prev[idx];
4726 gfx.raw_keys_prev[idx] = down;
4727 down && !was
4728 },
4729 None => false,
4730 }
4731 }
4732 }
4733 #[cfg(not(windows))]
4734 {
4735 false
4736 }
4737 } else if gfx.input_suppressed() {
4738 false
4739 } else {
4740 gfx.window
4741 .as_ref()
4742 .and_then(|w| {
4743 str_to_minifb_key(&name)
4744 .map(|k| w.is_key_pressed(k, minifb::KeyRepeat::No))
4745 })
4746 .unwrap_or(false)
4747 }
4748 };
4749 let pressed =
4751 pressed || ((name == "enter" || name == "return") && gamepad::start_edge());
4752 return Ok(Value::Bool(pressed));
4753 }
4754 #[cfg(target_arch = "wasm32")]
4755 {
4756 let name = self.arg_str(&args, 0, "");
4757 let pressed = crate::gfx::wasm_is_key_pressed(&name);
4758 return Ok(Value::Bool(pressed));
4759 }
4760 },
4761
4762 "mouse_dx" | "เมาส์X" | "鼠ΔX" | "マウスΔX" | "마우스ΔX" | "دلتا_ماوس_ایکس" | "فارق_الفأرة_س" | "דלתא_עכבר_X" | "ماؤس_ڈیلٹا_ایکس" | "souris_dx" | "maus_dx" | "мышь_dx" => {
4764 #[cfg(not(target_arch = "wasm32"))]
4765 return Ok(Value::Number(self.gfx.borrow().mouse_dx as f64));
4766 #[cfg(target_arch = "wasm32")]
4767 return Ok(Value::Number(crate::gfx::wasm_mouse_dx() as f64));
4768 },
4769 #[cfg(not(target_arch = "wasm32"))]
4771 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" | "غلتک_ماوس" | "عجلة_الفأرة" | "גלגלת_עכבר" | "ماؤس_اسکرول" =>
4772 {
4773 let gfx = self.gfx.borrow();
4774 let s = gfx
4775 .window
4776 .as_ref()
4777 .and_then(|w| w.get_scroll_wheel())
4778 .map(|(_, y)| y as f64)
4779 .unwrap_or(0.0);
4780 return Ok(Value::Number(s));
4781 },
4782 #[cfg(target_arch = "wasm32")]
4783 "mouse_scroll" | "ล้อเมาส์" | "滚轮" | "ホイール" | "스크롤" | "غلتک_ماوس" | "عجلة_الفأرة" | "גלגלת_עכבר" | "ماؤس_اسکرول" =>
4784 {
4785 return Ok(Value::Number(0.0));
4786 },
4787 "mouse_dy" | "เมาส์Y" | "鼠ΔY" | "マウスΔY" | "마우스ΔY" | "دلتا_ماوس_ایگرگ" | "فارق_الفأرة_ص" | "דלתא_עכבר_Y" | "ماؤس_ڈیلٹا_وائی" | "souris_dy" | "maus_dy" | "мышь_dy" => {
4788 #[cfg(not(target_arch = "wasm32"))]
4789 return Ok(Value::Number(self.gfx.borrow().mouse_dy as f64));
4790 #[cfg(target_arch = "wasm32")]
4791 return Ok(Value::Number(crate::gfx::wasm_mouse_dy() as f64));
4792 },
4793
4794 "pad_poll" | "手柄轮询" | "パッド更新" | "패드폴링" | "อัปเดตแพด" | "بررسی_دسته" | "استطلع_اليد" | "בדוק_בקר" | "گیم_پیڈ_پول" | "interroger_manette" | "gamepad_abfragen" | "опросить_геймпад" =>
4797 {
4798 #[cfg(not(target_arch = "wasm32"))]
4799 return Ok(Value::Number(self.pad_poll() as f64));
4800 #[cfg(target_arch = "wasm32")]
4801 return Ok(Value::Number(input_web::poll() as f64));
4802 },
4803 "pad_count" | "手柄数" | "パッド数" | "패드수" | "จำนวนแพด" | "تعداد_دسته" | "عدد_أيدي_التحكم" | "מספר_בקרים" | "گیم_پیڈ_تعداد" | "nombre_manettes" | "gamepad_anzahl" | "число_геймпадов" =>
4805 {
4806 #[cfg(not(target_arch = "wasm32"))]
4807 {
4808 let inp = self.input.borrow();
4809 let n = inp.as_ref().map_or(0, |s| s.sensorium.devices.count());
4810 return Ok(Value::Number(n as f64));
4811 }
4812 #[cfg(target_arch = "wasm32")]
4813 return Ok(Value::Number(input_web::count() as f64));
4814 },
4815 "pad_connected" | "手柄连接" | "パッド接続" | "패드연결" | "แพดเชื่อม" | "دسته_متصل" | "يد_التحكم_متصلة" | "בקר_מחובר" | "گیم_پیڈ_منسلک" | "manette_connectée" | "gamepad_verbunden" | "геймпад_подключён" =>
4817 {
4818 #[cfg(not(target_arch = "wasm32"))]
4819 {
4820 let i = self.arg_num(&args, 0, 0.0)? as usize;
4821 let inp = self.input.borrow();
4822 let c = inp
4823 .as_ref()
4824 .is_some_and(|s| s.sensorium.devices.for_player(i as u8).is_some());
4825 return Ok(Value::Bool(c));
4826 }
4827 #[cfg(target_arch = "wasm32")]
4828 {
4829 let i = self.arg_num(&args, 0, 0.0)? as usize;
4830 return Ok(Value::Bool(input_web::is_connected(i)));
4831 }
4832 },
4833 "pad_button" | "手柄按键" | "パッドボタン" | "패드버튼" | "ปุ่มแพด" | "دکمه_دسته" | "زر_اليد" | "כפתור_בקר" | "گیم_پیڈ_بٹن" | "bouton_manette" | "gamepad_taste" | "кнопка_геймпада" =>
4835 {
4836 #[cfg(not(target_arch = "wasm32"))]
4837 {
4838 let i = self.arg_num(&args, 0, 0.0)? as usize;
4839 let name = self.arg_str(&args, 1, "");
4840 let down = parse_pad_button(&name)
4841 .is_some_and(|b| self.with_pad(i, false, |p| p.is_down(b)));
4842 return Ok(Value::Bool(down));
4843 }
4844 #[cfg(target_arch = "wasm32")]
4845 {
4846 let i = self.arg_num(&args, 0, 0.0)? as usize;
4847 let name = self.arg_str(&args, 1, "");
4848 return Ok(Value::Bool(input_web::button_down(i, &name)));
4849 }
4850 },
4851 "pad_pressed" | "手柄按下" | "パッド押下" | "패드눌림" | "แพดกด" | "دکمه_دسته_فشرده" | "زر_اليد_مضغوط" | "כפתור_בקר_לחוץ" | "گیم_پیڈ_دبایا" | "manette_appuyée" | "gamepad_gedrückt" | "геймпад_нажат" =>
4854 {
4855 #[cfg(not(target_arch = "wasm32"))]
4856 {
4857 let i = self.arg_num(&args, 0, 0.0)? as usize;
4858 let name = self.arg_str(&args, 1, "");
4859 let p = parse_pad_button(&name)
4860 .is_some_and(|b| self.with_pad(i, false, |g| g.just_pressed(b)));
4861 return Ok(Value::Bool(p));
4862 }
4863 #[cfg(target_arch = "wasm32")]
4864 {
4865 let i = self.arg_num(&args, 0, 0.0)? as usize;
4866 let name = self.arg_str(&args, 1, "");
4867 return Ok(Value::Bool(input_web::button_down(i, &name)));
4868 }
4869 },
4870 "pad_lx" | "手柄左X" | "パッド左X" | "패드왼X" | "แพดซ้ายX" | "آنالوگ_چپ_ایکس" | "عصا_اليسرى_س" | "ג'ויסטיק_שמאל_X" | "بائیں_اسٹک_ایکس" | "manette_axe_gauche_x" | "gamepad_lx" | "геймпад_ось_лево_x" => {
4872 #[cfg(not(target_arch = "wasm32"))]
4873 {
4874 let i = self.arg_num(&args, 0, 0.0)? as usize;
4875 return Ok(Value::Number(
4876 self.with_pad(i, 0.0, |p| p.left_stick.x as f64),
4877 ));
4878 }
4879 #[cfg(target_arch = "wasm32")]
4880 {
4881 let i = self.arg_num(&args, 0, 0.0)? as usize;
4882 return Ok(Value::Number(input_web::axis_lx(i) as f64));
4883 }
4884 },
4885 "pad_ly" | "手柄左Y" | "パッド左Y" | "패드왼Y" | "แพดซ้ายY" | "آنالوگ_چپ_ایگرگ" | "عصا_اليسرى_ص" | "ג'ויסטיק_שמאל_Y" | "بائیں_اسٹک_وائی" | "manette_axe_gauche_y" | "gamepad_ly" | "геймпад_ось_лево_y" => {
4886 #[cfg(not(target_arch = "wasm32"))]
4887 {
4888 let i = self.arg_num(&args, 0, 0.0)? as usize;
4889 return Ok(Value::Number(
4890 self.with_pad(i, 0.0, |p| p.left_stick.y as f64),
4891 ));
4892 }
4893 #[cfg(target_arch = "wasm32")]
4894 {
4895 let i = self.arg_num(&args, 0, 0.0)? as usize;
4896 return Ok(Value::Number(input_web::axis_ly(i) as f64));
4897 }
4898 },
4899 "pad_rx" | "手柄右X" | "パッド右X" | "패드오X" | "แพดขวาX" | "آنالوگ_راست_ایکس" | "عصا_اليمنى_س" | "ג'ויסטיק_ימין_X" | "دائیں_اسٹک_ایکس" | "manette_axe_droit_x" | "gamepad_rx" | "геймпад_ось_право_x" => {
4900 #[cfg(not(target_arch = "wasm32"))]
4901 {
4902 let i = self.arg_num(&args, 0, 0.0)? as usize;
4903 return Ok(Value::Number(
4904 self.with_pad(i, 0.0, |p| p.right_stick.x as f64),
4905 ));
4906 }
4907 #[cfg(target_arch = "wasm32")]
4908 {
4909 let i = self.arg_num(&args, 0, 0.0)? as usize;
4910 return Ok(Value::Number(input_web::axis_rx(i) as f64));
4911 }
4912 },
4913 "pad_ry" | "手柄右Y" | "パッド右Y" | "패드오Y" | "แพดขวาY" | "آنالوگ_راست_ایگرگ" | "عصا_اليمنى_ص" | "ג'ויסטיק_ימין_Y" | "دائیں_اسٹک_وائی" | "manette_axe_droit_y" | "gamepad_ry" | "геймпад_ось_право_y" => {
4914 #[cfg(not(target_arch = "wasm32"))]
4915 {
4916 let i = self.arg_num(&args, 0, 0.0)? as usize;
4917 return Ok(Value::Number(
4918 self.with_pad(i, 0.0, |p| p.right_stick.y as f64),
4919 ));
4920 }
4921 #[cfg(target_arch = "wasm32")]
4922 {
4923 let i = self.arg_num(&args, 0, 0.0)? as usize;
4924 return Ok(Value::Number(input_web::axis_ry(i) as f64));
4925 }
4926 },
4927 "pad_lt" | "手柄左扳机" | "パッド左トリガー" | "패드왼트리거" | "ไกแพดซ้าย" | "ماشه_چپ" | "زناد_اليسار" | "הדק_שמאל" | "بائیں_ٹریگر" | "manette_gâchette_gauche" | "gamepad_lt" | "геймпад_триггер_лево" =>
4929 {
4930 #[cfg(not(target_arch = "wasm32"))]
4931 {
4932 let i = self.arg_num(&args, 0, 0.0)? as usize;
4933 return Ok(Value::Number(
4934 self.with_pad(i, 0.0, |p| p.left_trigger as f64),
4935 ));
4936 }
4937 #[cfg(target_arch = "wasm32")]
4938 {
4939 let i = self.arg_num(&args, 0, 0.0)? as usize;
4940 return Ok(Value::Number(input_web::trigger_lt(i) as f64));
4941 }
4942 },
4943 "pad_rt" | "手柄右扳机" | "パッド右トリガー" | "패드오트리거" | "ไกแพดขวา" | "ماشه_راست" | "زناد_اليمين" | "הדק_ימין" | "دائیں_ٹریگر" | "manette_gâchette_droite" | "gamepad_rt" | "геймпад_триггер_право" =>
4944 {
4945 #[cfg(not(target_arch = "wasm32"))]
4946 {
4947 let i = self.arg_num(&args, 0, 0.0)? as usize;
4948 return Ok(Value::Number(
4949 self.with_pad(i, 0.0, |p| p.right_trigger as f64),
4950 ));
4951 }
4952 #[cfg(target_arch = "wasm32")]
4953 {
4954 let i = self.arg_num(&args, 0, 0.0)? as usize;
4955 return Ok(Value::Number(input_web::trigger_rt(i) as f64));
4956 }
4957 },
4958 "pad_rumble" | "手柄震动" | "パッド振動" | "패드진동" | "แพดสั่น" | "لرزش_دسته" | "اهتزاز_اليد" | "רטט_בקר" | "گیم_پیڈ_تھرتھراہٹ" | "vibration_manette" | "gamepad_vibration" | "вибрация_геймпада" =>
4960 {
4961 #[cfg(not(target_arch = "wasm32"))]
4962 {
4963 use ling_input::backend::InputBackend;
4964 let i = self.arg_num(&args, 0, 0.0)? as usize;
4965 let lo = self.arg_num(&args, 1, 0.0)? as f32;
4966 let hi = self.arg_num(&args, 2, lo as f64)? as f32;
4967 let mut inp = self.input.borrow_mut();
4968 if let Some(s) = inp.as_mut() {
4969 if let Some(dev) = s.sensorium.devices.for_player(i as u8).map(|d| d.id) {
4970 s.backend.set_rumble(
4971 dev,
4972 ling_input::Rumble { low: lo, high: hi, ..Default::default() },
4973 );
4974 }
4975 }
4976 return Ok(Value::Unit);
4977 }
4978 #[cfg(target_arch = "wasm32")]
4979 return Ok(Value::Unit);
4980 },
4981
4982 "set_camera_pos" | "ตั้งตำแหน่งกล้อง" | "镜坐标" | "カメラ座標" | "카메라좌표" | "تنظیم_موقعیت_دوربین" | "عيّن_موضع_الكاميرا" | "קבע_מיקום_מצלמה" | "کیمرہ_مقام_مقرر_کرو" | "définir_position_caméra" | "kameraposition_setzen" | "задать_позицию_камеры" =>
4984 {
4985 let x = self.arg_num(&args, 0, 0.0)? as f32;
4986 let y = self.arg_num(&args, 1, 0.0)? as f32;
4987 let z = self.arg_num(&args, 2, 0.0)? as f32;
4988 {
4989 let mut gfx = self.gfx.borrow_mut();
4990 gfx.camera.tx = x;
4991 gfx.camera.ty = y;
4992 gfx.camera.tz = z;
4993 }
4994 #[cfg(not(target_arch = "wasm32"))]
4995 if let Some(audio) = &self.audio {
4996 audio.set_listener_pos(x, y, z);
4997 }
4998 return Ok(Value::Unit);
4999 },
5000
5001 "move_camera" => {
5003 let dx = self.arg_num(&args, 0, 0.0)? as f32;
5004 let dy = self.arg_num(&args, 1, 0.0)? as f32;
5005 let dz = self.arg_num(&args, 2, 0.0)? as f32;
5006 let mut gfx = self.gfx.borrow_mut();
5007 gfx.camera.tx += dx;
5008 gfx.camera.ty += dy;
5009 gfx.camera.tz += dz;
5010 return Ok(Value::Unit);
5011 },
5012
5013 "set_zdist" | "ตั้งระยะห่าง" | "镜距" | "Z距離設定" | "Z거리설정" | "تنظیم_فاصله_عمق" | "عيّن_مسافة_العمق" | "קבע_מרחק_עומק" | "گہرائی_فاصلہ_مقرر_کرو" | "définir_distance_z" | "z_abstand_setzen" | "задать_дистанцию_z" =>
5015 {
5016 let d = self.arg_num(&args, 0, 5.0)? as f32;
5017 self.gfx.borrow_mut().camera.zdist = d;
5018 return Ok(Value::Unit);
5019 },
5020
5021 "capture_mouse" | "จับเมาส์" | "捕鼠" | "マウス捕捉" | "마우스잡기" | "ضبط_ماوس" | "امسك_الفأرة" | "לכוד_עכבר" | "ماؤس_پکڑو" | "capturer_souris" | "maus_erfassen" | "захватить_мышь" =>
5023 {
5024 #[cfg(not(target_arch = "wasm32"))]
5025 {
5026 let mut gfx = self.gfx.borrow_mut();
5027 gfx.mouse_captured = true;
5028 gfx.last_mx = f32::NAN;
5029 if let Some(win) = gfx.window.as_mut() {
5030 win.set_cursor_visibility(false);
5031 }
5032 }
5033 return Ok(Value::Unit);
5034 },
5035
5036 "release_mouse" => {
5038 #[cfg(not(target_arch = "wasm32"))]
5039 {
5040 let mut gfx = self.gfx.borrow_mut();
5041 gfx.mouse_captured = false;
5042 gfx.last_mx = f32::NAN;
5043 if let Some(win) = gfx.window.as_mut() {
5044 win.set_cursor_visibility(true);
5045 }
5046 #[cfg(windows)]
5047 unsafe {
5048 extern "system" {
5050 fn ClipCursor(lpRect: *const std::ffi::c_void) -> i32;
5051 }
5052 ClipCursor(std::ptr::null());
5053 }
5054 }
5055 return Ok(Value::Unit);
5056 },
5057
5058 "cursor_hide" => {
5064 #[cfg(not(target_arch = "wasm32"))]
5065 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5066 win.set_cursor_visibility(false);
5067 }
5068 return Ok(Value::Unit);
5069 },
5070 "cursor_show" => {
5071 #[cfg(not(target_arch = "wasm32"))]
5072 if let Some(win) = self.gfx.borrow_mut().window.as_mut() {
5073 win.set_cursor_visibility(true);
5074 }
5075 return Ok(Value::Unit);
5076 },
5077
5078 "set_camera" | "ตั้งกล้อง" | "设镜" | "设置摄像机" | "カメラ設定" | "카메라설정" | "تنظیم_دوربین" | "عيّن_الكاميرا" | "קבע_מצלמה" | "کیمرہ_مقرر_کرو" | "définir_caméra" | "kamera_setzen" | "задать_камеру" =>
5085 {
5086 let cry = self.arg_num(&args, 0, 1.0)? as f32;
5087 let sry = self.arg_num(&args, 1, 0.0)? as f32;
5088 let crx = self.arg_num(&args, 2, 1.0)? as f32;
5089 let srx = self.arg_num(&args, 3, 0.0)? as f32;
5090 let mut gfx = self.gfx.borrow_mut();
5091 gfx.camera.cry = cry;
5092 gfx.camera.sry = sry;
5093 gfx.camera.crx = crx;
5094 gfx.camera.srx = srx;
5095 return Ok(Value::Unit);
5096 },
5097
5098 "set_projection" | "ตั้งโปรเจกชัน" | "投影" | "投影設定" | "투영설정" | "تنظیم_فرافکنی" | "عيّن_الإسقاط" | "קבע_הטלה" | "پروجیکشن_مقرر_کرو" | "définir_projection" | "projektion_setzen" | "задать_проекцию" =>
5101 {
5102 let cx = self.arg_num(&args, 0, 960.0)? as f32;
5103 let cy = self.arg_num(&args, 1, 540.0)? as f32;
5104 let focal = self.arg_num(&args, 2, 1080.0)? as f32;
5105 let zdist = self.arg_num(&args, 3, 5.0)? as f32;
5106 let mut gfx = self.gfx.borrow_mut();
5107 gfx.camera.cx = cx;
5108 gfx.camera.cy = cy;
5109 gfx.camera.focal = focal;
5110 gfx.camera.zdist = zdist;
5111 return Ok(Value::Unit);
5112 },
5113
5114 "gltf_load" => {
5116 let path = self.arg_str(&args, 0, "");
5117 match ling_physics::gltf::GltfModel::load(&path) {
5118 Ok(m) => {
5119 self.gltf_models.borrow_mut().push(m);
5120 let h = self.gltf_models.borrow().len() - 1;
5121 return Ok(Value::Number(h as f64));
5122 }
5123 Err(e) => {
5124 eprintln!("mesh_load failed ({path}): {e}");
5125 return Ok(Value::Number(-1.0));
5126 }
5127 }
5128 },
5129 "gltf_anim_count" => {
5131 let h = self.arg_num(&args, 0, -1.0)? as i64;
5132 let n = self
5133 .gltf_models
5134 .borrow()
5135 .get(h as usize)
5136 .map(|m| m.animations.len())
5137 .unwrap_or(0);
5138 return Ok(Value::Number(n as f64));
5139 },
5140 "gltf_anim_name" => {
5142 let h = self.arg_num(&args, 0, -1.0)? as i64;
5143 let i = self.arg_num(&args, 1, 0.0)? as usize;
5144 let s = self
5145 .gltf_models
5146 .borrow()
5147 .get(h as usize)
5148 .and_then(|m| m.animations.get(i))
5149 .map(|a| a.name.clone())
5150 .unwrap_or_default();
5151 return Ok(Value::Str(s));
5152 },
5153 "gltf_anim_dur" => {
5155 let h = self.arg_num(&args, 0, -1.0)? as i64;
5156 let i = self.arg_num(&args, 1, 0.0)? as usize;
5157 let d = self
5158 .gltf_models
5159 .borrow()
5160 .get(h as usize)
5161 .and_then(|m| m.animations.get(i))
5162 .map(|a| a.duration)
5163 .unwrap_or(0.0);
5164 return Ok(Value::Number(d as f64));
5165 },
5166 "gltf_tris" => {
5168 let h = self.arg_num(&args, 0, -1.0)? as i64;
5169 let n: usize = self
5170 .gltf_models
5171 .borrow()
5172 .get(h as usize)
5173 .map(|m| m.meshes.iter().map(|mm| mm.indices.len()).sum::<usize>() / 3)
5174 .unwrap_or(0);
5175 return Ok(Value::Number(n as f64));
5176 },
5177
5178 "gltf_joint_count" => {
5180 let h = self.arg_num(&args, 0, -1.0)? as i64;
5181 let n = self
5182 .gltf_models
5183 .borrow()
5184 .get(h as usize)
5185 .and_then(|m| m.skins.first())
5186 .map(|s| s.joints.len())
5187 .unwrap_or(0);
5188 return Ok(Value::Number(n as f64));
5189 },
5190 "gltf_joint_name" => {
5192 let h = self.arg_num(&args, 0, -1.0)? as i64;
5193 let j = self.arg_num(&args, 1, 0.0)? as usize;
5194 let models = self.gltf_models.borrow();
5195 let s = models
5196 .get(h as usize)
5197 .and_then(|m| {
5198 m.skins
5199 .first()
5200 .and_then(|sk| sk.joints.get(j))
5201 .and_then(|jt| m.nodes.get(jt.node_idx))
5202 .map(|n| n.name.clone())
5203 })
5204 .unwrap_or_default();
5205 return Ok(Value::Str(s));
5206 },
5207
5208 "gltf_draw" => {
5213 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5214 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5215 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5216 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5217 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5218 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5219 let (sy, cyy) = yaw.sin_cos();
5220 let models = self.gltf_models.borrow();
5221 let model = match models.get(hh as usize) {
5222 Some(m) => m,
5223 None => return Ok(Value::Unit),
5224 };
5225 let mut gfx = self.gfx.borrow_mut();
5226 let cp = {
5227 let c = &gfx.camera;
5228 ling_gpu::CameraParams {
5229 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5230 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5231 tx: c.tx, ty: c.ty, tz: c.tz,
5232 }
5233 };
5234 let near = -gfx.camera.zdist + 0.02;
5235 let ambient = gfx.ambient;
5236 for mesh in &model.meshes {
5237 let nlow = mesh.name.to_lowercase();
5238 let base: u32 = if nlow.contains("hair") {
5239 0x7a4a28
5240 } else if nlow.contains("cloth") || nlow.contains("top") {
5241 0x4a86e0
5242 } else if nlow.contains("wing") {
5243 0xe6ecf5
5244 } else if nlow.contains("star") {
5245 0xffd24d
5246 } else {
5247 0xf2d6b8
5248 };
5249 let nv = mesh.verts.len();
5250 if nv == 0 {
5251 continue;
5252 }
5253 let mut world = vec![0.0f32; nv * 3];
5254 for (i, v) in mesh.verts.iter().enumerate() {
5255 let gx = v.pos.x * scale;
5256 let gy = -v.pos.y * scale;
5257 let gz = -v.pos.z * scale;
5258 let rx = gx * cyy + gz * sy;
5259 let rz = -gx * sy + gz * cyy;
5260 world[i * 3] = ox + rx;
5261 world[i * 3 + 1] = oy + gy;
5262 world[i * 3 + 2] = oz + rz;
5263 }
5264 let mut proj = vec![0.0f32; nv * 3];
5265 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5266 let idx = &mesh.indices;
5267 let nt = idx.len() / 3;
5268 for t in 0..nt {
5269 let ia = idx[t * 3] as usize;
5270 let ib = idx[t * 3 + 1] as usize;
5271 let ic = idx[t * 3 + 2] as usize;
5272 if ia >= nv || ib >= nv || ic >= nv {
5273 continue;
5274 }
5275 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5276 if (da + db + dc) / 3.0 <= near {
5277 continue;
5278 }
5279 let col = {
5280 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5281 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5282 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5283 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5284 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5285 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5286 let centroid =
5287 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5288 if gfx.flat_shade {
5289 base
5290 } else {
5291 crate::gfx::light::compute_lit_color(
5292 base, normal, centroid, &gfx.lights, ambient,
5293 )
5294 }
5295 };
5296 let depth = (da + db + dc) / 3.0;
5297 let col = gfx.fog_apply(col, depth);
5298 gfx.depth_queue.push_triangle_zv(
5299 col,
5300 proj[ia * 3], proj[ia * 3 + 1], da,
5301 proj[ib * 3], proj[ib * 3 + 1], db,
5302 proj[ic * 3], proj[ic * 3 + 1], dc,
5303 );
5304 }
5305 }
5306 return Ok(Value::Unit);
5307 },
5308
5309 "gltf_autorig" => {
5311 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5312 let mut models = self.gltf_models.borrow_mut();
5313 if let Some(m) = models.get_mut(hh as usize) {
5314 return Ok(Value::Number(m.autorig() as f64));
5315 }
5316 return Ok(Value::Number(0.0));
5317 },
5318
5319 "gltf_pose_draw" => {
5323 let hh = self.arg_num(&args, 0, -1.0)? as i64;
5324 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5325 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5326 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5327 let scale = self.arg_num(&args, 4, 1.0)? as f32;
5328 let yaw = self.arg_num(&args, 5, 0.0)? as f32;
5329 let euler: Vec<f32> = match args.get(6) {
5330 Some(Value::List(v)) => {
5331 v.iter().map(|x| self.to_number(x).unwrap_or(0.0) as f32).collect()
5332 },
5333 _ => Vec::new(),
5334 };
5335 let (sy, cyy) = yaw.sin_cos();
5336 let models = self.gltf_models.borrow();
5337 let model = match models.get(hh as usize) {
5338 Some(m) => m,
5339 None => return Ok(Value::Unit),
5340 };
5341 let skinned = model.skin_local(&euler);
5342 let mut gfx = self.gfx.borrow_mut();
5343 let cp = {
5344 let c = &gfx.camera;
5345 ling_gpu::CameraParams {
5346 cry: c.cry, sry: c.sry, crx: c.crx, srx: c.srx,
5347 cx: c.cx, cy: c.cy, focal: c.focal, zdist: c.zdist,
5348 tx: c.tx, ty: c.ty, tz: c.tz,
5349 }
5350 };
5351 let near = -gfx.camera.zdist + 0.02;
5352 let ambient = gfx.ambient;
5353 for (mi, mesh) in model.meshes.iter().enumerate() {
5354 let nlow = mesh.name.to_lowercase();
5355 let base: u32 = if nlow.contains("hair") {
5356 0x7a4a28
5357 } else if nlow.contains("cloth") || nlow.contains("top") {
5358 0x4a86e0
5359 } else if nlow.contains("wing") {
5360 0xe6ecf5
5361 } else if nlow.contains("star") {
5362 0xffd24d
5363 } else {
5364 0xf2d6b8
5365 };
5366 let sk = match skinned.get(mi) {
5367 Some(s) => s,
5368 None => continue,
5369 };
5370 let nv = sk.len();
5371 if nv == 0 {
5372 continue;
5373 }
5374 let mut world = vec![0.0f32; nv * 3];
5375 for i in 0..nv {
5376 let gx = sk[i][0] * scale;
5377 let gy = -sk[i][1] * scale;
5378 let gz = -sk[i][2] * scale;
5379 let rx = gx * cyy + gz * sy;
5380 let rz = -gx * sy + gz * cyy;
5381 world[i * 3] = ox + rx;
5382 world[i * 3 + 1] = oy + gy;
5383 world[i * 3 + 2] = oz + rz;
5384 }
5385 let mut proj = vec![0.0f32; nv * 3];
5386 ling_gpu::backend().project_points(&world, &cp, &mut proj);
5387 let idx = &mesh.indices;
5388 let nt = idx.len() / 3;
5389 for t in 0..nt {
5390 let ia = idx[t * 3] as usize;
5391 let ib = idx[t * 3 + 1] as usize;
5392 let ic = idx[t * 3 + 2] as usize;
5393 if ia >= nv || ib >= nv || ic >= nv {
5394 continue;
5395 }
5396 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5397 if (da + db + dc) / 3.0 <= near {
5398 continue;
5399 }
5400 let col = {
5401 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5402 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5403 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5404 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5405 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5406 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5407 let centroid =
5408 [(ax + bx + px) / 3.0, (ay + by + py) / 3.0, (az + bz + pz) / 3.0];
5409 if gfx.flat_shade {
5410 base
5411 } else {
5412 crate::gfx::light::compute_lit_color(
5413 base, normal, centroid, &gfx.lights, ambient,
5414 )
5415 }
5416 };
5417 let depth = (da + db + dc) / 3.0;
5418 let col = gfx.fog_apply(col, depth);
5419 gfx.depth_queue.push_triangle_zv(
5420 col,
5421 proj[ia * 3], proj[ia * 3 + 1], da,
5422 proj[ib * 3], proj[ib * 3 + 1], db,
5423 proj[ic * 3], proj[ic * 3 + 1], dc,
5424 );
5425 }
5426 }
5427 return Ok(Value::Unit);
5428 },
5429
5430 "draw_mesh" | "วาดเมช" | "رسم_مش" | "ارسم_شبكة" | "צייר_רשת" | "میش_کھینچو" | "dessiner_maillage" | "netz_zeichnen" | "рисовать_меш" => {
5437 let pos = match args.first() {
5438 Some(Value::List(v)) => v,
5439 _ => return Ok(Value::Unit),
5440 };
5441 let idx = match args.get(1) {
5442 Some(Value::List(v)) => v,
5443 _ => return Ok(Value::Unit),
5444 };
5445 let ox = self.arg_num(&args, 2, 0.0)? as f32;
5446 let oy = self.arg_num(&args, 3, 0.0)? as f32;
5447 let oz = self.arg_num(&args, 4, 0.0)? as f32;
5448 let scale = self.arg_num(&args, 5, 1.0)? as f32;
5449 let mode = self.arg_num(&args, 6, 0.0)? as i64;
5450 let nv = pos.len() / 3;
5451 if nv == 0 {
5452 return Ok(Value::Unit);
5453 }
5454 let mut world = vec![0.0f32; nv * 3];
5455 for i in 0..nv {
5456 world[i * 3] = ox + self.to_number(&pos[i * 3]).unwrap_or(0.0) as f32 * scale;
5457 world[i * 3 + 1] =
5458 oy + self.to_number(&pos[i * 3 + 1]).unwrap_or(0.0) as f32 * scale;
5459 world[i * 3 + 2] =
5460 oz + self.to_number(&pos[i * 3 + 2]).unwrap_or(0.0) as f32 * scale;
5461 }
5462 let mut gfx = self.gfx.borrow_mut();
5463 let cp = {
5464 let c = &gfx.camera;
5465 ling_gpu::CameraParams {
5466 cry: c.cry,
5467 sry: c.sry,
5468 crx: c.crx,
5469 srx: c.srx,
5470 cx: c.cx,
5471 cy: c.cy,
5472 focal: c.focal,
5473 zdist: c.zdist,
5474 tx: c.tx,
5475 ty: c.ty,
5476 tz: c.tz,
5477 }
5478 };
5479 let near = -gfx.camera.zdist + 0.02;
5480 let base = gfx.color;
5481 let ambient = gfx.ambient;
5482 let mut proj = vec![0.0f32; nv * 3]; ling_gpu::backend().project_points(&world, &cp, &mut proj);
5484 let nt = idx.len() / 3;
5485 for t in 0..nt {
5486 let ia = self.to_number(&idx[t * 3]).unwrap_or(0.0) as usize;
5487 let ib = self.to_number(&idx[t * 3 + 1]).unwrap_or(0.0) as usize;
5488 let ic = self.to_number(&idx[t * 3 + 2]).unwrap_or(0.0) as usize;
5489 if ia >= nv || ib >= nv || ic >= nv {
5490 continue;
5491 }
5492 let (da, db, dc) = (proj[ia * 3 + 2], proj[ib * 3 + 2], proj[ic * 3 + 2]);
5493 if (da + db + dc) / 3.0 <= near {
5494 continue;
5495 } let col = if mode == 1 {
5497 let h = t as f32 * 0.6;
5498 let r = ((h.sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5499 let g = (((h + 2.094).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5500 let b = (((h + 4.189).sin() * 0.5 + 0.5) * 150.0 + 55.0) as u32;
5501 (r << 16) | (g << 8) | b
5502 } else {
5503 let (ax, ay, az) = (world[ia * 3], world[ia * 3 + 1], world[ia * 3 + 2]);
5504 let (bx, by, bz) = (world[ib * 3], world[ib * 3 + 1], world[ib * 3 + 2]);
5505 let (px, py, pz) = (world[ic * 3], world[ic * 3 + 1], world[ic * 3 + 2]);
5506 let (ux, uy, uz) = (bx - ax, by - ay, bz - az);
5507 let (vx, vy, vz) = (px - ax, py - ay, pz - az);
5508 let normal = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
5509 let centroid = [
5510 (ax + bx + px) / 3.0,
5511 (ay + by + py) / 3.0,
5512 (az + bz + pz) / 3.0,
5513 ];
5514 if gfx.flat_shade {
5515 base
5516 } else {
5517 crate::gfx::light::compute_lit_color(
5518 base,
5519 normal,
5520 centroid,
5521 &gfx.lights,
5522 ambient,
5523 )
5524 }
5525 };
5526 let depth = (da + db + dc) / 3.0;
5527 let col = gfx.fog_apply(col, depth);
5528 gfx.depth_queue.push_triangle_zv(
5532 col,
5533 proj[ia * 3],
5534 proj[ia * 3 + 1],
5535 da,
5536 proj[ib * 3],
5537 proj[ib * 3 + 1],
5538 db,
5539 proj[ic * 3],
5540 proj[ic * 3 + 1],
5541 dc,
5542 );
5543 }
5544 return Ok(Value::Unit);
5545 },
5546
5547 "add_light" | "เพิ่มแสง" | "加灯" | "ライト追加" | "조명추가" | "افزودن_نور" | "أضف_ضوء" | "הוסף_אור" | "روشنی_شامل_کرو" | "ajouter_lumière" | "licht_hinzufügen" | "добавить_свет" =>
5551 {
5552 let x = self.arg_num(&args, 0, 0.0)? as f32;
5553 let y = self.arg_num(&args, 1, -3.0)? as f32;
5554 let z = self.arg_num(&args, 2, 3.0)? as f32;
5555 let mut r = self.arg_num(&args, 3, 1.0)? as f32;
5556 let mut g = self.arg_num(&args, 4, 1.0)? as f32;
5557 let mut b = self.arg_num(&args, 5, 1.0)? as f32;
5558 if r > 1.5 || g > 1.5 || b > 1.5 {
5561 r /= 255.0;
5562 g /= 255.0;
5563 b /= 255.0;
5564 }
5565 let intensity = self.arg_num(&args, 6, 1.0)? as f32;
5566 let radius = self.arg_num(&args, 7, 0.0)? as f32;
5567 self.gfx
5568 .borrow_mut()
5569 .lights
5570 .push(Light { x, y, z, r, g, b, intensity, radius });
5571 return Ok(Value::Unit);
5572 },
5573
5574 "clear_lights" | "ล้างแสง" | "清灯" | "ライト消去" | "조명초기화" | "پاککردن_نورها" | "امسح_الأضواء" | "נקה_אורות" | "روشنیاں_صاف_کرو" | "effacer_lumières" | "lichter_löschen" | "очистить_свет" =>
5576 {
5577 self.gfx.borrow_mut().lights.clear();
5578 return Ok(Value::Unit);
5579 },
5580
5581 "set_material" | "ตั้งวัสดุ" | "设置材质" | "マテリアル設定" | "재질설정" | "تنظیم_متریال" | "عيّن_المادة" | "קבע_חומר" | "میٹریل_مقرر_کرو" =>
5591 {
5592 let key = self.arg_str(&args, 0, "");
5593 let val = self.arg_num(&args, 1, 0.0)?;
5594 let mut gfx = self.gfx.borrow_mut();
5595 let mat = gfx
5596 .material
5597 .get_or_insert_with(crate::gfx::LingMaterial::default);
5598 match key.as_str() {
5599 "albedo" => mat.albedo = val as u32,
5600 "roughness" => mat.roughness = val as f32,
5601 "metallic" => mat.metallic = val as f32,
5602 "emission" => mat.emission = val as u32,
5603 "emission_strength" => mat.emission_strength = val as f32,
5604 "specular" => mat.specular = val as f32,
5605 "specular_tint" => mat.specular_tint = val as f32,
5606 "subsurface" => mat.subsurface = val as f32,
5607 "subsurface_color" => mat.subsurface_color = val as u32,
5608 "clearcoat" => mat.clearcoat = val as f32,
5609 "clearcoat_roughness" => mat.clearcoat_roughness = val as f32,
5610 "transmission" => mat.transmission = val as f32,
5611 "ior" => mat.ior = val as f32,
5612 "iridescence" => mat.iridescence = val as f32,
5613 "sheen" => mat.sheen = val as f32,
5614 "anisotropy" => mat.anisotropy = val as f32,
5615 "anisotropy_angle" => mat.anisotropy_angle = val as f32,
5616 "toon_bands" => mat.toon_bands = val as u32,
5617 "shadow_softness" => mat.shadow_softness = val as f32,
5618 "outline_px" => mat.outline_px = val as f32,
5619 "outline_color" => mat.outline_color = val as u32,
5620 "highlight_color" => mat.highlight_color = val as u32,
5621 _ => {},
5622 }
5623 return Ok(Value::Unit);
5624 },
5625
5626 "reset_material" | "รีเซ็ตวัสดุ" | "重置材质" | "マテリアルリセット" | "재질초기화" | "بازنشانی_متریال" | "أعد_ضبط_المادة" | "אפס_חומר" | "میٹریل_ری_سیٹ" =>
5629 {
5630 self.gfx.borrow_mut().material = None;
5631 return Ok(Value::Unit);
5632 },
5633
5634 "toon_outlines"
5640 | "ตั้งเส้นขอบการ์ตูน"
5641 | "卡通轮廓"
5642 | "トゥーンアウトライン"
5643 | "툰아웃라인" | "خطوط_کارتونی" | "حدود_كرتونية" | "קווי_מתאר_מצוירים" | "ٹون_آؤٹ_لائنز" => {
5644 let px = self.arg_num(&args, 0, 0.0)? as f32;
5645 let color = self.arg_num(&args, 1, 0.0)? as u32;
5646 let thresh = self.arg_num(&args, 2, 0.05)? as f32;
5647 let mut gfx = self.gfx.borrow_mut();
5648 gfx.toon.outline_px = px;
5649 gfx.toon.outline_color = color;
5650 gfx.toon.outline_thresh = thresh;
5651 return Ok(Value::Unit);
5652 },
5653
5654 "tone_stop" | "ตั้งจุดโทน" | "色调停止" | "トーンストップ" | "톤스톱" | "نقطه_توقف_تن_رنگ" | "نقطة_توقف_اللون" | "נקודת_עצירת_גוון" | "ٹون_اسٹاپ" =>
5660 {
5661 let t = self.arg_num(&args, 0, 0.0)? as f32;
5662 let val = self.arg_num(&args, 1, 1.0)? as f32;
5663 let mut gfx = self.gfx.borrow_mut();
5664 gfx.toon.ramp.stops.push(crate::gfx::toon::ToneStop {
5665 t: t.clamp(0.0, 1.0),
5666 value: val.clamp(0.0, 1.0),
5667 });
5668 gfx.toon
5669 .ramp
5670 .stops
5671 .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
5672 return Ok(Value::Unit);
5673 },
5674
5675 "tone_smooth" | "ตั้งโทนนุ่ม" | "色调平滑" | "トーンスムーズ" | "톤스무스" | "تن_رنگ_نرم" | "تدرج_لون_ناعم" | "גוון_חלק" | "ٹون_ہموار" =>
5678 {
5679 let v = self.arg_num(&args, 0, 0.0)? as f32;
5680 self.gfx.borrow_mut().toon.ramp.smooth = v > 0.5;
5681 return Ok(Value::Unit);
5682 },
5683
5684 "tone_bezier" | "ตั้งโทนเบซิเยร์" | "色调贝塞尔" | "トーンベジェ" | "톤베지어" | "تن_رنگ_بزیه" | "تدرج_لون_بيزيه" | "גוון_בזייה" | "بیزیئر_ٹون" =>
5692 {
5693 let y1 = self.arg_num(&args, 0, 1.0 / 3.0)? as f32;
5694 let y2 = self.arg_num(&args, 1, 2.0 / 3.0)? as f32;
5695 self.gfx.borrow_mut().toon.ramp.bezier = Some([y1, y2]);
5696 return Ok(Value::Unit);
5697 },
5698
5699 "tone_bezier_off"
5701 | "ปิดโทนเบซิเยร์"
5702 | "关闭色调贝塞尔"
5703 | "トーンベジェオフ"
5704 | "톤베지어끄기" | "خاموشکردن_بزیه" | "إيقاف_تدرج_بيزيه" | "כבה_גוון_בזייה" | "بیزیئر_ٹون_بند" => {
5705 self.gfx.borrow_mut().toon.ramp.bezier = None;
5706 return Ok(Value::Unit);
5707 },
5708
5709 "tone_ramp_reset"
5711 | "รีเซ็ตการไล่โทน"
5712 | "重置色调渐变"
5713 | "トーンランプリセット"
5714 | "톤램프리셋" | "بازنشانی_شیب_تن_رنگ" | "أعد_ضبط_تدرج_اللون" | "אפס_שיפוע_גוון" | "ٹون_ریمپ_ری_سیٹ" => {
5715 self.gfx.borrow_mut().toon.ramp = crate::gfx::toon::ToneRamp::default();
5716 return Ok(Value::Unit);
5717 },
5718
5719 "tone_ramp_clear"
5721 | "ล้างการไล่โทน"
5722 | "清除色调渐变"
5723 | "トーンランプクリア"
5724 | "톤램프클리어" | "پاککردن_شیب_تن_رنگ" | "امسح_تدرج_اللون" | "נקה_שיפוע_גוון" | "ٹون_ریمپ_صاف" => {
5725 self.gfx.borrow_mut().toon.ramp.stops.clear();
5726 return Ok(Value::Unit);
5727 },
5728
5729 "tone_soft" | "โทนขอบนุ่ม" | "色调柔边" | "トーンソフト" | "톤소프트" | "تن_رنگ_لبهنرم" | "تدرج_ناعم_الحواف" | "גוון_קצה_רך" | "نرم_کنارہ_ٹون" | "tonalité_douce" | "weicher_ton" | "мягкий_тон" => {
5737 let s = self.arg_num(&args, 0, 0.32)? as f32;
5738 let sh = self.arg_num(&args, 1, 0.65)? as f32;
5739 let mut gfx = self.gfx.borrow_mut();
5740 gfx.toon.ramp.soft = s.clamp(0.0, 1.0);
5741 gfx.toon.ramp.sheen = sh.clamp(0.0, 1.0);
5742 return Ok(Value::Unit);
5743 },
5744
5745 "set_ssao" | "ตั้งเงาสัมผัส" | "环境光遮蔽" | "アンビエントオクルージョン"
5750 | "앰비언트오클루전" | "تنظیم_انسداد_محیطی" | "عيّن_تظليل_محيطي" | "קבע_הצללה_סביבתית" | "ایس_ایس_اے_او_مقرر_کرو" | "définir_ssao" | "ssao_setzen" | "задать_ssao" => {
5751 let s = self.arg_num(&args, 0, 0.35)? as f32;
5752 let r = self.arg_num(&args, 1, 6.0)? as f32;
5753 let z = self.arg_num(&args, 2, 12.0)? as f32;
5754 let mut gfx = self.gfx.borrow_mut();
5755 gfx.toon.ao_strength = s.clamp(0.0, 1.0);
5756 gfx.toon.ao_radius = r.max(1.0);
5757 gfx.toon.ao_range = z.max(0.01);
5758 return Ok(Value::Unit);
5759 },
5760
5761 "set_fxaa" | "ลบรอยหยัก" | "屏幕抗锯齿" | "画面アンチエイリアス" | "화면안티앨리어싱" | "تنظیم_ضدلبهدندانه_سریع" | "عيّن_مضاد_التسنن_السريع" | "קבע_החלקת_מסך" | "ایف_ایکس_اے_اے_مقرر_کرو" | "définir_fxaa" | "fxaa_setzen" | "задать_fxaa" => {
5767 let on = self.arg_num(&args, 0, 1.0)? as i64 != 0;
5768 self.gfx.borrow_mut().toon.fxaa = on;
5769 return Ok(Value::Unit);
5770 },
5771
5772 "set_bloom" | "ตั้งบลูม" | "泛光" | "ブルーム" | "블룸" | "تنظیم_درخشش" | "عيّن_التوهج" | "קבע_זוהר" | "بلوم_مقرر_کرو" | "définir_bloom" | "bloom_setzen" | "задать_блум" => {
5777 let s = self.arg_num(&args, 0, 0.45)? as f32;
5778 let t = self.arg_num(&args, 1, 0.74)? as f32;
5779 let mut gfx = self.gfx.borrow_mut();
5780 gfx.toon.bloom_strength = s.max(0.0);
5781 gfx.toon.bloom_thresh = t.clamp(0.0, 0.99);
5782 return Ok(Value::Unit);
5783 },
5784
5785 "shadow_smooth" | "ตั้งเงานุ่ม" | "柔化阴影" | "影ソフト" | "그림자부드럽게" | "سایه_نرم" | "ظل_ناعم" | "צל_חלק" | "نرم_سایہ" =>
5788 {
5789 let s = self.arg_num(&args, 0, 0.0)? as f32;
5790 let mut gfx = self.gfx.borrow_mut();
5791 gfx.toon.ramp.smooth = s > 0.05;
5792 if s > 0.05 {
5793 let y1 = (0.333 + s * 0.2).clamp(0.0, 1.0);
5794 let y2 = (0.667 - s * 0.2).clamp(0.0, 1.0);
5795 gfx.toon.ramp.bezier = Some([y1, y2]);
5796 } else {
5797 gfx.toon.ramp.bezier = None;
5798 }
5799 return Ok(Value::Unit);
5800 },
5801
5802 "toon_highlight"
5804 | "ตั้งไฮไลท์การ์ตูน"
5805 | "卡通高光"
5806 | "トゥーンハイライト"
5807 | "툰하이라이트" | "هایلایت_کارتونی" | "إبراز_كرتوني" | "הדגשה_מצוירת" | "ٹون_ہائی_لائٹ" => {
5808 let _strength = self.arg_num(&args, 0, 0.0)? as f32;
5810 let _thresh = self.arg_num(&args, 2, 0.78)? as f32;
5811 return Ok(Value::Unit);
5813 },
5814
5815 "set_ambient" | "ตั้งแสงรอบข้าง" | "环境光" | "環境光設定" | "환경광설정" | "تنظیم_نور_محیطی" | "عيّن_الإضاءة_المحيطة" | "קבע_תאורה_סביבתית" | "ماحولیاتی_روشنی_مقرر_کرو" | "définir_ambiante" | "umgebungslicht_setzen" | "задать_фон" =>
5817 {
5818 let v = self.arg_num(&args, 0, 0.15)? as f32;
5819 self.gfx.borrow_mut().ambient = v;
5820 return Ok(Value::Unit);
5821 },
5822
5823 "set_fog" | "ตั้งหมอก" | "雾" | "霧設定" | "안개설정" | "تنظیم_مه" | "عيّن_الضباب" | "קבע_ערפל" | "دھند_مقرر_کرو" => {
5826 let r = self.arg_num(&args, 0, 0.0)?.clamp(0.0, 255.0) as u32;
5827 let g = self.arg_num(&args, 1, 0.0)?.clamp(0.0, 255.0) as u32;
5828 let b = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 255.0) as u32;
5829 let start = self.arg_num(&args, 3, 0.0)? as f32;
5830 let end = self.arg_num(&args, 4, 0.0)? as f32;
5831 let mut gfx = self.gfx.borrow_mut();
5832 gfx.fog_color = (r << 16) | (g << 8) | b;
5833 gfx.fog_start = start;
5834 gfx.fog_end = end;
5835 return Ok(Value::Unit);
5836 },
5837
5838 "วาดสามเหลี่ยม3มิติ" | "draw_triangle_3d" | "triangle3d" | "رسم_مثلث_سهبعدی" | "ارسم_مثلثا_ثلاثي_الأبعاد" | "צייר_משולש_תלת_ממדי" | "تھری_ڈی_مثلث_کھینچو" =>
5842 {
5843 let ax = self.arg_num(&args, 0, 0.0)? as f32;
5844 let ay = self.arg_num(&args, 1, 0.0)? as f32;
5845 let az = self.arg_num(&args, 2, 0.0)? as f32;
5846 let bx = self.arg_num(&args, 3, 0.0)? as f32;
5847 let by = self.arg_num(&args, 4, 0.0)? as f32;
5848 let bz = self.arg_num(&args, 5, 0.0)? as f32;
5849 let cx = self.arg_num(&args, 6, 0.0)? as f32;
5850 let cy = self.arg_num(&args, 7, 0.0)? as f32;
5851 let cz = self.arg_num(&args, 8, 0.0)? as f32;
5852
5853 let mut gfx = self.gfx.borrow_mut();
5854
5855 if gfx.mesh_capture.is_some() {
5857 let col = gfx.color;
5858 gfx.mesh_capture
5859 .as_mut()
5860 .unwrap()
5861 .push(([ax, ay, az, bx, by, bz, cx, cy, cz], col));
5862 return Ok(Value::Unit);
5863 }
5864
5865 gfx.submit_triangle(ax, ay, az, bx, by, bz, cx, cy, cz);
5866 return Ok(Value::Unit);
5867 },
5868
5869 "เริ่มอบเมช" | "mesh_bake_begin" | "شروع_پخت_مش" | "ابدأ_خبز_الشبكة" | "התחל_אפיית_רשת" | "میش_بیک_شروع" => {
5871 self.gfx.borrow_mut().mesh_capture = Some(Vec::new());
5872 return Ok(Value::Unit);
5873 },
5874
5875 "เมชแคชรับ" | "mesh_cache_get" | "دریافت_کش_مش" | "اجلب_مخبأ_الشبكة" | "קבל_מטמון_רשת" | "میش_کیش_حاصل_کرو" => {
5877 let key = self.arg_num(&args, 0, 0.0)? as i64;
5878 let h = self.gfx.borrow().mesh_cache.get(&key).copied();
5879 return Ok(Value::Number(h.map(|x| x as f64).unwrap_or(-1.0)));
5880 },
5881
5882 "เมชแคชตั้ง" | "mesh_cache_put" | "ذخیره_در_کش_مش" | "ضع_في_مخبأ_الشبكة" | "שמור_במטמון_רשת" | "میش_کیش_رکھو" => {
5884 let key = self.arg_num(&args, 0, 0.0)? as i64;
5885 let h = self.arg_num(&args, 1, 0.0)? as usize;
5886 let mut gfx = self.gfx.borrow_mut();
5887 const CAP: usize = 256;
5888 if gfx.mesh_cache.len() >= CAP {
5889 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5890 gfx.mesh_cache.clear();
5891 for id in evict {
5892 if id < gfx.meshes.len() {
5893 gfx.meshes[id].clear();
5894 gfx.mesh_free.push(id);
5895 }
5896 }
5897 }
5898 gfx.mesh_cache.insert(key, h);
5899 return Ok(Value::Unit);
5900 },
5901
5902 "เมชแคชล้าง" | "mesh_cache_clear" | "پاککردن_کش_مش" | "امسح_مخبأ_الشبكة" | "נקה_מטמון_רשת" | "میش_کیش_صاف" => {
5904 let mut gfx = self.gfx.borrow_mut();
5905 let evict: Vec<usize> = gfx.mesh_cache.values().copied().collect();
5906 gfx.mesh_cache.clear();
5907 for id in evict {
5908 if id < gfx.meshes.len() {
5909 gfx.meshes[id].clear();
5910 gfx.mesh_free.push(id);
5911 }
5912 }
5913 return Ok(Value::Unit);
5914 },
5915
5916 "จบอบเมช" | "mesh_bake_end" | "پایان_پخت_مش" | "أنهِ_خبز_الشبكة" | "סיים_אפיית_רשת" | "میش_بیک_ختم" => {
5918 let mut gfx = self.gfx.borrow_mut();
5919 let tris = gfx.mesh_capture.take().unwrap_or_default();
5920 let id = gfx.mesh_register(tris);
5921 return Ok(Value::Number(id as f64));
5922 },
5923
5924 "วาดอบเมช" | "mesh_bake_draw" | "วาดอบเมชสี" | "mesh_bake_draw_col" | "رسم_مش_پخته" | "ارسم_شبكة_مخبوزة" | "צייר_רשת_אפויה" | "بیکڈ_میش_کھینچو" =>
5928 {
5929 let baked_col = matches!(name, "วาดอบเมชสี" | "mesh_bake_draw_col");
5930 let id = self.arg_num(&args, 0, 0.0)? as usize;
5931 let ox = self.arg_num(&args, 1, 0.0)? as f32;
5932 let oy = self.arg_num(&args, 2, 0.0)? as f32;
5933 let oz = self.arg_num(&args, 3, 0.0)? as f32;
5934 let rx = self.arg_num(&args, 4, 1.0)? as f32;
5935 let ry = self.arg_num(&args, 5, 0.0)? as f32;
5936 let rz = self.arg_num(&args, 6, 0.0)? as f32;
5937 let ux = self.arg_num(&args, 7, 0.0)? as f32;
5938 let uy = self.arg_num(&args, 8, 1.0)? as f32;
5939 let uz = self.arg_num(&args, 9, 0.0)? as f32;
5940 let s = self.arg_num(&args, 10, 1.0)? as f32;
5941 self.gfx
5942 .borrow_mut()
5943 .mesh_draw(id, ox, oy, oz, rx, ry, rz, ux, uy, uz, s, baked_col);
5944 return Ok(Value::Unit);
5945 },
5946
5947 "draw_quad_3d"
5951 | "quad3d"
5952 | "วาดสี่เหลี่ยม3มิติ"
5953 | "draw_pent_3d"
5954 | "pent3d"
5955 | "วาดห้าเหลี่ยม3มิติ"
5956 | "draw_hex_3d"
5957 | "hex3d"
5958 | "วาดหกเหลี่ยม3มิติ"
5959 | "draw_polygon_3d"
5960 | "polygon3d"
5961 | "วาดรูปหลายเหลี่ยม3มิติ" | "رسم_چهارضلعی_سهبعدی" | "ارسم_رباعيا_ثلاثي_الأبعاد" | "צייר_מרובע_תלת_ממדי" | "تھری_ڈی_چوکور_کھینچو" => {
5962 let mut wxs: [f32; 8] = [0.0; 8];
5964 let mut wys: [f32; 8] = [0.0; 8];
5965 let mut wzs: [f32; 8] = [0.0; 8];
5966 let n_verts;
5967
5968 if args.len() == 1 {
5969 let list = match &args[0] {
5971 Value::List(l) => l.clone(),
5972 _ => {
5973 return Err(EvalErr::from("draw_polygon_3d: expected list".to_string()))
5974 },
5975 };
5976 let coords: Vec<f32> = list
5977 .iter()
5978 .map(|v| match v {
5979 Value::Number(n) => *n as f32,
5980 _ => 0.0,
5981 })
5982 .collect();
5983 n_verts = (coords.len() / 3).min(8);
5984 for i in 0..n_verts {
5985 wxs[i] = coords[i * 3];
5986 wys[i] = coords[i * 3 + 1];
5987 wzs[i] = coords[i * 3 + 2];
5988 }
5989 } else {
5990 n_verts = (args.len() / 3).min(8);
5992 for i in 0..n_verts {
5993 wxs[i] = self.arg_num(&args, i * 3, 0.0)? as f32;
5994 wys[i] = self.arg_num(&args, i * 3 + 1, 0.0)? as f32;
5995 wzs[i] = self.arg_num(&args, i * 3 + 2, 0.0)? as f32;
5996 }
5997 }
5998 if n_verts < 3 {
5999 return Ok(Value::Unit);
6000 }
6001
6002 let mut gfx = self.gfx.borrow_mut();
6003
6004 if gfx.mesh_capture.is_some() {
6010 let col = gfx.color;
6011 let cap = gfx.mesh_capture.as_mut().unwrap();
6012 for i in 1..n_verts - 1 {
6013 cap.push((
6014 [
6015 wxs[0], wys[0], wzs[0],
6016 wxs[i], wys[i], wzs[i],
6017 wxs[i + 1], wys[i + 1], wzs[i + 1],
6018 ],
6019 col,
6020 ));
6021 }
6022 return Ok(Value::Unit);
6023 }
6024
6025 let normal = crate::gfx::poly::face_normal(
6027 wxs[0], wys[0], wzs[0], wxs[1], wys[1], wzs[1], wxs[2], wys[2], wzs[2],
6028 );
6029
6030 let mut wcs: [u32; 8] = [0; 8];
6032 if gfx.flat_shade {
6033 let c = gfx.color;
6034 for wc in wcs.iter_mut().take(n_verts) {
6035 *wc = c;
6036 }
6037 } else if let Some(ref mat) = gfx.material.clone() {
6038 let cam = [gfx.camera.cx, gfx.camera.cy, gfx.camera.zdist];
6039 let lights: Vec<_> = gfx.lights.clone();
6040 let ambient = gfx.ambient;
6041 for i in 0..n_verts {
6042 let v = [wxs[i], wys[i], wzs[i]];
6043 let vd = [cam[0] - v[0], cam[1] - v[1], cam[2] - v[2]];
6044 wcs[i] = crate::gfx::material::shade(mat, normal, vd, v, &lights, ambient);
6045 }
6046 } else {
6047 let base = gfx.color;
6048 let lights: Vec<_> = gfx.lights.clone();
6049 let ambient = gfx.ambient;
6050 for i in 0..n_verts {
6051 wcs[i] = crate::gfx::light::compute_lit_color_linear(
6052 base,
6053 normal,
6054 [wxs[i], wys[i], wzs[i]],
6055 &lights,
6056 ambient,
6057 );
6058 }
6059 }
6060
6061 let near = -gfx.camera.zdist + 0.05;
6063 let mut clip_in: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6064 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6065 for i in 0..n_verts {
6066 let d = gfx.camera.depth(wxs[i], wys[i], wzs[i]);
6067 clip_in[i] = (wxs[i], wys[i], wzs[i], d, wcs[i]);
6068 }
6069 let mut clip_out: [(f32, f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6070 [(0.0, 0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6071 let pn = crate::gfx::poly::clip_near(&clip_in, n_verts, near, &mut clip_out);
6072 if pn < 3 {
6073 return Ok(Value::Unit);
6074 }
6075
6076 let mut proj: [(f32, f32, f32, u32); crate::gfx::poly::MAX_CLIP_VERTS] =
6078 [(0.0, 0.0, 0.0, 0); crate::gfx::poly::MAX_CLIP_VERTS];
6079 for i in 0..pn {
6080 let (sx, sy, sz) =
6081 gfx.camera
6082 .project(clip_out[i].0, clip_out[i].1, clip_out[i].2);
6083 let fc = gfx.fog_apply(clip_out[i].4, sz);
6084 proj[i] = (sx, sy, sz, fc);
6085 }
6086
6087 let unlit = gfx.flat_shade;
6089 crate::gfx::poly::fan_emit_proj(
6090 &proj,
6091 pn,
6092 |x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2| {
6093 gfx.depth_queue.push_triangle_g_zv(
6094 x0, y0, z0, c0, x1, y1, z1, c1, x2, y2, z2, c2, 3, unlit,
6095 );
6096 },
6097 );
6098 return Ok(Value::Unit);
6099 },
6100
6101 "วาดเส้น3มิติ" | "draw_line_3d" | "line3d" | "画3D线" | "3D線描く" | "3D선그리기" | "رسم_خط_سهبعدی" | "ارسم_خطا_ثلاثي_الأبعاد" | "צייר_קו_תלת_ממדי" | "تھری_ڈی_لکیر_کھینچو" =>
6105 {
6106 let ax = self.arg_num(&args, 0, 0.0)? as f32;
6107 let ay = self.arg_num(&args, 1, 0.0)? as f32;
6108 let az = self.arg_num(&args, 2, 0.0)? as f32;
6109 let bx = self.arg_num(&args, 3, 0.0)? as f32;
6110 let by = self.arg_num(&args, 4, 0.0)? as f32;
6111 let bz = self.arg_num(&args, 5, 0.0)? as f32;
6112
6113 let mut gfx = self.gfx.borrow_mut();
6114 let color = gfx.color;
6115 let near = -gfx.camera.zdist + 0.05;
6117 let mut lax = ax;
6118 let mut lay = ay;
6119 let mut laz = az;
6120 let mut lbx = bx;
6121 let mut lby = by;
6122 let mut lbz = bz;
6123 let da_raw = gfx.camera.depth(lax, lay, laz);
6124 let db_raw = gfx.camera.depth(lbx, lby, lbz);
6125 if da_raw <= near && db_raw <= near {
6126 return Ok(Value::Unit);
6127 }
6128 if da_raw <= near {
6129 let t = (near - da_raw) / (db_raw - da_raw);
6130 lax += t * (lbx - lax);
6131 lay += t * (lby - lay);
6132 laz += t * (lbz - laz);
6133 } else if db_raw <= near {
6134 let t = (near - da_raw) / (db_raw - da_raw);
6135 lbx = lax + t * (lbx - lax);
6136 lby = lay + t * (lby - lay);
6137 lbz = laz + t * (lbz - laz);
6138 }
6139 if !gfx.edge_set.try_insert(lax, lay, laz, lbx, lby, lbz) {
6141 return Ok(Value::Unit);
6142 }
6143 let (sax, say, da) = gfx.camera.project(lax, lay, laz);
6144 let (sbx, sby, db) = gfx.camera.project(lbx, lby, lbz);
6145 let depth = (da + db) / 2.0;
6146 let color = gfx.fog_apply(color, depth);
6147 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
6148 return Ok(Value::Unit);
6149 },
6150
6151 #[cfg(not(target_arch = "wasm32"))]
6161 "orb_shell" | "球壳" | "オーブ殻" | "오브껍질" | "เปลือกทรงกลม" | "پوسته_کروی" | "قشرة_كروية" | "קליפת_כדור" | "کروی_خول" | "coque_orbe" | "orb_hülle" | "оболочка_сферы" =>
6162 {
6163 let cx = self.arg_num(&args, 0, 0.)? as f32;
6164 let cy = self.arg_num(&args, 1, 0.)? as f32;
6165 let cz = self.arg_num(&args, 2, 0.)? as f32;
6166 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6167 let ry = self.arg_num(&args, 4, 0.)? as f32;
6168 let rx = self.arg_num(&args, 5, 0.)? as f32;
6169 let density = (self.arg_num(&args, 6, 10.)? as i32).clamp(1, 48);
6170 let tr = (self.arg_num(&args, 7, 230.)? as f32).clamp(0., 255.);
6171 let tg = (self.arg_num(&args, 8, 230.)? as f32).clamp(0., 255.);
6172 let tb = (self.arg_num(&args, 9, 235.)? as f32).clamp(0., 255.);
6173 let (cyr, syr) = (ry.cos(), ry.sin());
6174 let (cxr, sxr) = (rx.cos(), rx.sin());
6175 let tau = std::f32::consts::TAU;
6176 let pi = std::f32::consts::PI;
6177 let turns = 6.0_f32; let nseg = 96; let inv_r = if radius.abs() > 1e-5 {
6180 1.0 / radius
6181 } else {
6182 0.0
6183 };
6184 let pt = |u: f32, theta0: f32, dir: f32| -> ([f32; 3], f32) {
6187 let phi = pi * u; let th = dir * turns * tau * u + theta0;
6189 let (mut x, y, mut z) = (
6190 phi.sin() * th.cos() * radius,
6191 phi.cos() * radius,
6192 phi.sin() * th.sin() * radius,
6193 );
6194 let x1 = x * cyr + z * syr; let z1 = -x * syr + z * cyr;
6196 x = x1;
6197 z = z1;
6198 let y2 = y * cxr - z * sxr; let z2 = y * sxr + z * cxr;
6200 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.0, 1.0);
6202 ([cx + x, cy + y2, cz + z2], facing)
6203 };
6204 let mut gfx = self.gfx.borrow_mut();
6205 let near = -gfx.camera.zdist + 0.05;
6206 let seg = |gfx: &mut crate::gfx::GfxState, a: [f32; 3], b: [f32; 3], lum: f32| {
6208 let (mut lax, mut lay, mut laz) = (a[0], a[1], a[2]);
6209 let (mut lbx, mut lby, mut lbz) = (b[0], b[1], b[2]);
6210 let da = gfx.camera.depth(lax, lay, laz);
6211 let db = gfx.camera.depth(lbx, lby, lbz);
6212 if da <= near && db <= near {
6213 return;
6214 }
6215 if da <= near {
6216 let t = (near - da) / (db - da);
6217 lax += t * (lbx - lax);
6218 lay += t * (lby - lay);
6219 laz += t * (lbz - laz);
6220 } else if db <= near {
6221 let t = (near - da) / (db - da);
6222 lbx = lax + t * (lbx - lax);
6223 lby = lay + t * (lby - lay);
6224 lbz = laz + t * (lbz - laz);
6225 }
6226 let (sax, say, da2) = gfx.camera.project(lax, lay, laz);
6227 let (sbx, sby, db2) = gfx.camera.project(lbx, lby, lbz);
6228 let l = (0.12 + 0.88 * lum).clamp(0.0, 1.0);
6230 let cr = (tr * l) as u32;
6231 let cg = (tg * l) as u32;
6232 let cb = (tb * l) as u32;
6233 let color = (cr << 16) | (cg << 8) | cb;
6234 gfx.depth_queue
6235 .push_line((da2 + db2) * 0.5, color, sax, say, sbx, sby);
6236 };
6237 for &dir in &[1.0_f32, -1.0_f32] {
6239 for s in 0..density {
6240 let theta0 = s as f32 * tau / density as f32;
6241 let mut prev = pt(0.0, theta0, dir);
6242 for k in 1..=nseg {
6243 let cur = pt(k as f32 / nseg as f32, theta0, dir);
6244 seg(&mut gfx, prev.0, cur.0, (prev.1 + cur.1) * 0.5);
6245 prev = cur;
6246 }
6247 }
6248 }
6249 return Ok(Value::Unit);
6250 },
6251
6252 #[cfg(not(target_arch = "wasm32"))]
6260 "orb_particles" | "球内粒子" | "オーブ粒子" | "오브입자" | "อนุภาคทรงกลม" | "ذرات_کروی" | "جسيمات_كروية" | "חלקיקי_כדור" | "کروی_ذرات" | "particules_orbe" | "orb_partikel" | "частицы_сферы" =>
6261 {
6262 let cx = self.arg_num(&args, 0, 0.)? as f32;
6263 let cy = self.arg_num(&args, 1, 0.)? as f32;
6264 let cz = self.arg_num(&args, 2, 0.)? as f32;
6265 let radius = self.arg_num(&args, 3, 1.0)? as f32;
6266 let count = (self.arg_num(&args, 4, 160.)? as i32).clamp(1, 4000);
6267 let t = self.arg_num(&args, 5, 0.)? as f32;
6268 let tr = (self.arg_num(&args, 6, 255.)? as f32).clamp(0., 255.);
6269 let tg = (self.arg_num(&args, 7, 255.)? as f32).clamp(0., 255.);
6270 let tb = (self.arg_num(&args, 8, 255.)? as f32).clamp(0., 255.);
6271 let inv_r = if radius.abs() > 1e-5 {
6272 1.0 / radius
6273 } else {
6274 0.0
6275 };
6276 let h = |mut x: u32| -> f32 {
6278 x = x.wrapping_mul(747796405).wrapping_add(2891336453);
6279 x = ((x >> ((x >> 28).wrapping_add(4))) ^ x).wrapping_mul(277803737);
6280 (((x >> 22) ^ x) & 0xFFFFFF) as f32 / 16_777_216.0
6281 };
6282 let tau = std::f32::consts::TAU;
6283 let (cyr, syr) = ((t * 0.5).cos(), (t * 0.5).sin());
6285 let (cxr, sxr) = ((t * 0.23).cos(), (t * 0.23).sin());
6286 let mut gfx = self.gfx.borrow_mut();
6287 let near = -gfx.camera.zdist + 0.05;
6288 let (sw, sh) = (gfx.width as i32, gfx.height as i32);
6289 for i in 0..count {
6290 let i = i as u32;
6291 let u = h(i.wrapping_mul(3) + 1);
6293 let rr = u.cbrt() * radius * (0.85 + 0.15 * (t * 1.3 + i as f32).sin()); let th = h(i.wrapping_mul(3) + 2) * tau + t * (0.3 + 0.5 * h(i * 7 + 5)); let ph = (h(i.wrapping_mul(3) + 3) * 2.0 - 1.0).acos(); let (mut x, y, mut z) = (
6297 rr * ph.sin() * th.cos(),
6298 rr * ph.cos(),
6299 rr * ph.sin() * th.sin(),
6300 );
6301 let x1 = x * cyr + z * syr;
6303 let z1 = -x * syr + z * cyr;
6304 x = x1;
6305 z = z1;
6306 let y2 = y * cxr - z * sxr;
6307 let z2 = y * sxr + z * cxr;
6308 let (wx, wy, wz) = (cx + x, cy + y2, cz + z2);
6309 if gfx.camera.depth(wx, wy, wz) <= near {
6310 continue;
6311 }
6312 let (sx, sy, dep) = gfx.camera.project(wx, wy, wz);
6313 let sxi = sx as i32;
6314 let syi = sy as i32;
6315 if sxi < 0 || syi < 0 || sxi >= sw || syi >= sh {
6316 continue;
6317 }
6318 let facing = (0.5 - 0.5 * z2 * inv_r).clamp(0.15, 1.0);
6320 let l = facing;
6321 let cr = (tr * l) as u32;
6322 let cg = (tg * l) as u32;
6323 let cb = (tb * l) as u32;
6324 let color = (cr << 16) | (cg << 8) | cb;
6325 let len = if facing > 0.7 { 1.0 } else { 0.0 };
6327 gfx.depth_queue.push_line(dep, color, sx, sy, sx + len, sy);
6328 }
6329 return Ok(Value::Unit);
6330 },
6331
6332 "project_3d" | "投影3D" | "3D投影" | "3D투영" | "ฉาย3มิติ" | "فرافکنی_سهبعدی" | "إسقاط_ثلاثي_الأبعاد" | "הטלה_תלת_ממדית" | "تھری_ڈی_پروجیکشن" | "projeter_3d" | "projizieren_3d" | "проекция_3d" => {
6336 let x = self.arg_num(&args, 0, 0.0)? as f32;
6337 let y = self.arg_num(&args, 1, 0.0)? as f32;
6338 let z = self.arg_num(&args, 2, 0.0)? as f32;
6339 let gfx = self.gfx.borrow();
6340 let near = -gfx.camera.zdist + 0.05;
6341 let d = gfx.camera.depth(x, y, z);
6342 if d <= near {
6343 return Ok(Value::List(Rc::new(vec![
6344 Value::Number(-99999.0),
6345 Value::Number(-99999.0),
6346 Value::Number(d as f64),
6347 ])));
6348 }
6349 let (sx, sy, depth) = gfx.camera.project(x, y, z);
6350 return Ok(Value::List(Rc::new(vec![
6351 Value::Number(sx as f64),
6352 Value::Number(sy as f64),
6353 Value::Number(depth as f64),
6354 ])));
6355 },
6356
6357 #[cfg(not(target_arch = "wasm32"))]
6364 "mouse_ray" => {
6365 let gfx = self.gfx.borrow();
6366 let (mx, my) = gfx
6367 .window
6368 .as_ref()
6369 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
6370 .unwrap_or((gfx.camera.cx, gfx.camera.cy));
6371 let cam = &gfx.camera;
6372 let dcx = (mx - cam.cx) / cam.focal;
6375 let dcy = (my - cam.cy) / cam.focal;
6376 let a_x = dcx;
6379 let a_y = cam.crx * dcy + cam.srx * 1.0;
6380 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6381 let dir_x = cam.cry * a_x + cam.sry * a_z;
6382 let dir_y = a_y;
6383 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6384 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6385 .sqrt()
6386 .max(1e-6);
6387 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6388 let ox = cam.tx;
6400 let oy = cam.ty;
6401 let oz = cam.tz;
6402 return Ok(Value::List(Rc::new(vec![
6403 Value::Number(ox as f64),
6404 Value::Number(oy as f64),
6405 Value::Number(oz as f64),
6406 Value::Number(dir_x as f64),
6407 Value::Number(dir_y as f64),
6408 Value::Number(dir_z as f64),
6409 ])));
6410 },
6411 #[cfg(target_arch = "wasm32")]
6412 "mouse_ray" => {
6413 let gfx = self.gfx.borrow();
6414 let mx = crate::gfx::wasm_mouse_x();
6415 let my = crate::gfx::wasm_mouse_y();
6416 let cam = &gfx.camera;
6417 let dcx = (mx - cam.cx) / cam.focal;
6418 let dcy = (my - cam.cy) / cam.focal;
6419 let a_x = dcx;
6420 let a_y = cam.crx * dcy + cam.srx * 1.0;
6421 let a_z = 0.0 - cam.srx * dcy + cam.crx * 1.0;
6422 let dir_x = cam.cry * a_x + cam.sry * a_z;
6423 let dir_y = a_y;
6424 let dir_z = 0.0 - cam.sry * a_x + cam.cry * a_z;
6425 let dlen = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z)
6426 .sqrt()
6427 .max(1e-6);
6428 let (dir_x, dir_y, dir_z) = (dir_x / dlen, dir_y / dlen, dir_z / dlen);
6429 let ox = cam.tx;
6430 let oy = cam.ty;
6431 let oz = cam.tz;
6432 return Ok(Value::List(Rc::new(vec![
6433 Value::Number(ox as f64),
6434 Value::Number(oy as f64),
6435 Value::Number(oz as f64),
6436 Value::Number(dir_x as f64),
6437 Value::Number(dir_y as f64),
6438 Value::Number(dir_z as f64),
6439 ])));
6440 },
6441 #[cfg(not(target_arch = "wasm32"))]
6444 "draw_poly" | "填充多边形" | "ポリゴン塗り" | "다각형채우기" | "เติมรูปหลายเหลี่ยม" | "رسم_چندضلعی" | "ارسم_مضلع" | "צייר_מצולע" | "کثیر_الاضلاع_کھینچو" | "dessiner_polygone" | "polygon_zeichnen" | "рисовать_полигон" =>
6445 {
6446 let mut pts: Vec<[f32; 2]> = Vec::new();
6447 if let Some(Value::List(v)) = args.first() {
6448 let mut i = 0;
6449 while i + 1 < v.len() {
6450 let x = self.to_number(&v[i]).unwrap_or(0.0) as f32;
6451 let y = self.to_number(&v[i + 1]).unwrap_or(0.0) as f32;
6452 pts.push([x, y]);
6453 i += 2;
6454 }
6455 }
6456 if pts.len() >= 3 {
6457 if pts[0] != pts[pts.len() - 1] {
6458 let p0 = pts[0];
6459 pts.push(p0);
6460 } let mut gfx = self.gfx.borrow_mut();
6462 let (w, h, color, add) = (gfx.width, gfx.height, gfx.color, gfx.blend == 1);
6463 crate::gfx::raster::fill_contours_aa(
6464 &mut gfx.buffer,
6465 w,
6466 h,
6467 color,
6468 add,
6469 std::slice::from_ref(&pts),
6470 );
6471 }
6472 return Ok(Value::Unit);
6473 },
6474
6475 "vtex_grid" | "ลายตาราง" | "纹格" | "格子模様" | "격자무늬" | "الگوی_شبکه" | "نقش_شبكة" | "דוגמת_רשת" | "نقش_جالی" | "motif_grille" | "muster_gitter" | "узор_сетка" =>
6484 {
6485 let cx = self.arg_num(&args, 0, 0.)? as f32;
6486 let cy = self.arg_num(&args, 1, 0.)? as f32;
6487 let cz = self.arg_num(&args, 2, 0.)? as f32;
6488 let ux = self.arg_num(&args, 3, 1.)? as f32;
6489 let uy = self.arg_num(&args, 4, 0.)? as f32;
6490 let uz = self.arg_num(&args, 5, 0.)? as f32;
6491 let vx = self.arg_num(&args, 6, 0.)? as f32;
6492 let vy = self.arg_num(&args, 7, 0.)? as f32;
6493 let vz = self.arg_num(&args, 8, 1.)? as f32;
6494 let cols = self.arg_num(&args, 9, 10.)? as usize;
6495 let rows = self.arg_num(&args, 10, 10.)? as usize;
6496 let cw = self.arg_num(&args, 11, 1.)? as f32;
6497 let ch = self.arg_num(&args, 12, 1.)? as f32;
6498 let fr = self.arg_num(&args, 13, 0.)? as f32;
6499 let hue = self.arg_num(&args, 14, 0.)? as f32;
6500 let mut gfx = self.gfx.borrow_mut();
6501 let cam = gfx.camera.clone();
6502 crate::gfx::vtex::draw_grid(
6503 &mut gfx.depth_queue,
6504 &cam,
6505 cx,
6506 cy,
6507 cz,
6508 ux,
6509 uy,
6510 uz,
6511 vx,
6512 vy,
6513 vz,
6514 cols,
6515 rows,
6516 cw,
6517 ch,
6518 fr,
6519 hue,
6520 );
6521 return Ok(Value::Unit);
6522 },
6523
6524 "vtex_rings" | "ลายวงซ้อน" | "纹环" | "同心円" | "동심원" | "الگوی_حلقه" | "نقش_حلقات" | "דוגמת_טבעות" | "نقش_حلقے" | "motif_anneaux" | "muster_ringe" | "узор_кольца" => {
6526 let cx = self.arg_num(&args, 0, 0.)? as f32;
6527 let cy = self.arg_num(&args, 1, 0.)? as f32;
6528 let cz = self.arg_num(&args, 2, 0.)? as f32;
6529 let ux = self.arg_num(&args, 3, 1.)? as f32;
6530 let uy = self.arg_num(&args, 4, 0.)? as f32;
6531 let uz = self.arg_num(&args, 5, 0.)? as f32;
6532 let vx = self.arg_num(&args, 6, 0.)? as f32;
6533 let vy = self.arg_num(&args, 7, 0.)? as f32;
6534 let vz = self.arg_num(&args, 8, 1.)? as f32;
6535 let nr = self.arg_num(&args, 9, 6.)? as usize;
6536 let ns = self.arg_num(&args, 10, 6.)? as usize;
6537 let mr = self.arg_num(&args, 11, 3.)? as f32;
6538 let tw = self.arg_num(&args, 12, 0.)? as f32;
6539 let fr = self.arg_num(&args, 13, 0.)? as f32;
6540 let hue = self.arg_num(&args, 14, 0.)? as f32;
6541 let mut gfx = self.gfx.borrow_mut();
6542 let cam = gfx.camera.clone();
6543 crate::gfx::vtex::draw_rings(
6544 &mut gfx.depth_queue,
6545 &cam,
6546 cx,
6547 cy,
6548 cz,
6549 ux,
6550 uy,
6551 uz,
6552 vx,
6553 vy,
6554 vz,
6555 nr,
6556 ns,
6557 mr,
6558 tw,
6559 fr,
6560 hue,
6561 );
6562 return Ok(Value::Unit);
6563 },
6564
6565 "vtex_star" | "ลายดาว" | "纹星" | "星模様" | "별무늬" | "الگوی_ستاره" | "نقش_نجمة" | "דוגמת_כוכב" | "نقش_ستارہ" | "motif_étoile" | "muster_stern" | "узор_звезда" => {
6567 let cx = self.arg_num(&args, 0, 0.)? as f32;
6568 let cy = self.arg_num(&args, 1, 0.)? as f32;
6569 let cz = self.arg_num(&args, 2, 0.)? as f32;
6570 let ux = self.arg_num(&args, 3, 1.)? as f32;
6571 let uy = self.arg_num(&args, 4, 0.)? as f32;
6572 let uz = self.arg_num(&args, 5, 0.)? as f32;
6573 let vx = self.arg_num(&args, 6, 0.)? as f32;
6574 let vy = self.arg_num(&args, 7, 0.)? as f32;
6575 let vz = self.arg_num(&args, 8, 1.)? as f32;
6576 let np = self.arg_num(&args, 9, 6.)? as usize;
6577 let ro = self.arg_num(&args, 10, 2.)? as f32;
6578 let ri = self.arg_num(&args, 11, 1.)? as f32;
6579 let rs = self.arg_num(&args, 12, 0.01)? as f32;
6580 let fr = self.arg_num(&args, 13, 0.)? as f32;
6581 let hue = self.arg_num(&args, 14, 0.)? as f32;
6582 let mut gfx = self.gfx.borrow_mut();
6583 let cam = gfx.camera.clone();
6584 crate::gfx::vtex::draw_star(
6585 &mut gfx.depth_queue,
6586 &cam,
6587 cx,
6588 cy,
6589 cz,
6590 ux,
6591 uy,
6592 uz,
6593 vx,
6594 vy,
6595 vz,
6596 np,
6597 ro,
6598 ri,
6599 rs,
6600 fr,
6601 hue,
6602 );
6603 return Ok(Value::Unit);
6604 },
6605
6606 "vtex_spiral" | "ลายเกลียว" | "纹螺" | "螺旋" | "나선" | "الگوی_مارپیچ" | "نقش_حلزوني" | "דוגמת_ספירלה" | "نقش_سرپیچ" | "motif_spirale" | "muster_spirale" | "узор_спираль" => {
6608 let cx = self.arg_num(&args, 0, 0.)? as f32;
6609 let cy = self.arg_num(&args, 1, 0.)? as f32;
6610 let cz = self.arg_num(&args, 2, 0.)? as f32;
6611 let ux = self.arg_num(&args, 3, 1.)? as f32;
6612 let uy = self.arg_num(&args, 4, 0.)? as f32;
6613 let uz = self.arg_num(&args, 5, 0.)? as f32;
6614 let vx = self.arg_num(&args, 6, 0.)? as f32;
6615 let vy = self.arg_num(&args, 7, 0.)? as f32;
6616 let vz = self.arg_num(&args, 8, 1.)? as f32;
6617 let nt = self.arg_num(&args, 9, 3.)? as f32;
6618 let mr = self.arg_num(&args, 10, 3.)? as f32;
6619 let st = self.arg_num(&args, 11, 120.)? as usize;
6620 let fr = self.arg_num(&args, 12, 0.)? as f32;
6621 let hue = self.arg_num(&args, 13, 0.)? as f32;
6622 let mut gfx = self.gfx.borrow_mut();
6623 let cam = gfx.camera.clone();
6624 crate::gfx::vtex::draw_spiral(
6625 &mut gfx.depth_queue,
6626 &cam,
6627 cx,
6628 cy,
6629 cz,
6630 ux,
6631 uy,
6632 uz,
6633 vx,
6634 vy,
6635 vz,
6636 nt,
6637 mr,
6638 st,
6639 fr,
6640 hue,
6641 );
6642 return Ok(Value::Unit);
6643 },
6644
6645 "vtex_flower" | "ลายดอก" | "纹花" | "花模様" | "꽃무늬" | "الگوی_گل" | "نقش_زهرة" | "דוגמת_פרח" | "نقش_پھول" | "motif_fleur" | "muster_blume" | "узор_цветок" => {
6647 let cx = self.arg_num(&args, 0, 0.)? as f32;
6648 let cy = self.arg_num(&args, 1, 0.)? as f32;
6649 let cz = self.arg_num(&args, 2, 0.)? as f32;
6650 let ux = self.arg_num(&args, 3, 1.)? as f32;
6651 let uy = self.arg_num(&args, 4, 0.)? as f32;
6652 let uz = self.arg_num(&args, 5, 0.)? as f32;
6653 let vx = self.arg_num(&args, 6, 0.)? as f32;
6654 let vy = self.arg_num(&args, 7, 0.)? as f32;
6655 let vz = self.arg_num(&args, 8, 1.)? as f32;
6656 let r = self.arg_num(&args, 9, 1.)? as f32;
6657 let ns = self.arg_num(&args, 10, 24.)? as usize;
6658 let fr = self.arg_num(&args, 11, 0.)? as f32;
6659 let hue = self.arg_num(&args, 12, 0.)? as f32;
6660 let mut gfx = self.gfx.borrow_mut();
6661 let cam = gfx.camera.clone();
6662 crate::gfx::vtex::draw_flower(
6663 &mut gfx.depth_queue,
6664 &cam,
6665 cx,
6666 cy,
6667 cz,
6668 ux,
6669 uy,
6670 uz,
6671 vx,
6672 vy,
6673 vz,
6674 r,
6675 ns,
6676 fr,
6677 hue,
6678 );
6679 return Ok(Value::Unit);
6680 },
6681
6682 "vtex_letter_rain" | "ลายอักษรไหล" | "纹字雨" | "文字雨" | "글자비" | "الگوی_باران_حروف" | "نقش_مطر_الحروف" | "דוגמת_גשם_אותיות" | "نقش_حروف_بارش" | "motif_pluie_lettres" | "muster_buchstabenregen" | "узор_дождь_букв" =>
6684 {
6685 let cx = self.arg_num(&args, 0, 0.)? as f32;
6686 let cy = self.arg_num(&args, 1, 0.)? as f32;
6687 let cz = self.arg_num(&args, 2, 0.)? as f32;
6688 let ux = self.arg_num(&args, 3, 1.)? as f32;
6689 let uy = self.arg_num(&args, 4, 0.)? as f32;
6690 let uz = self.arg_num(&args, 5, 0.)? as f32;
6691 let vx = self.arg_num(&args, 6, 0.)? as f32;
6692 let vy = self.arg_num(&args, 7, 0.)? as f32;
6693 let vz = self.arg_num(&args, 8, 1.)? as f32;
6694 let nc = self.arg_num(&args, 9, 16.)? as usize;
6695 let nv = self.arg_num(&args, 10, 14.)? as usize;
6696 let cw = self.arg_num(&args, 11, 0.65)? as f32;
6697 let rh = self.arg_num(&args, 12, 0.60)? as f32;
6698 let sp = self.arg_num(&args, 13, 0.025)? as f32;
6699 let fr = self.arg_num(&args, 14, 0.)? as f32;
6700 let hue = self.arg_num(&args, 15, 0.)? as f32;
6701 let mut gfx = self.gfx.borrow_mut();
6702 let cam = gfx.camera.clone();
6703 crate::gfx::vtex::draw_letter_rain(
6704 &mut gfx.depth_queue,
6705 &cam,
6706 cx,
6707 cy,
6708 cz,
6709 ux,
6710 uy,
6711 uz,
6712 vx,
6713 vy,
6714 vz,
6715 nc,
6716 nv,
6717 cw,
6718 rh,
6719 sp,
6720 fr,
6721 hue,
6722 );
6723 return Ok(Value::Unit);
6724 },
6725
6726 "vtex_hyperbolic_uv" | "ลายไฮเพอร์โบลิก" | "纹曲面" | "双曲線" | "쌍곡선" | "الگوی_هذلولی" | "نقش_زائدي" | "דוגמת_היפרבולית" | "نقش_ہائپربولک" | "motif_uv_hyperbolique" | "muster_hyperbolische_uv" | "узор_гиперболический_uv" =>
6728 {
6729 let cx = self.arg_num(&args, 0, 0.)? as f32;
6730 let cy = self.arg_num(&args, 1, 0.)? as f32;
6731 let cz = self.arg_num(&args, 2, 0.)? as f32;
6732 let ux = self.arg_num(&args, 3, 1.)? as f32;
6733 let uy = self.arg_num(&args, 4, 0.)? as f32;
6734 let uz = self.arg_num(&args, 5, 0.)? as f32;
6735 let vx = self.arg_num(&args, 6, 0.)? as f32;
6736 let vy = self.arg_num(&args, 7, 0.)? as f32;
6737 let vz = self.arg_num(&args, 8, 1.)? as f32;
6738 let mr = self.arg_num(&args, 9, 5.)? as f32;
6739 let nc = self.arg_num(&args, 10, 12.)? as usize;
6740 let nr = self.arg_num(&args, 11, 18.)? as usize;
6741 let fr = self.arg_num(&args, 12, 0.)? as f32;
6742 let hue = self.arg_num(&args, 13, 0.)? as f32;
6743 let mut gfx = self.gfx.borrow_mut();
6744 let cam = gfx.camera.clone();
6745 crate::gfx::vtex::draw_hyperbolic_uv(
6746 &mut gfx.depth_queue,
6747 &cam,
6748 cx,
6749 cy,
6750 cz,
6751 ux,
6752 uy,
6753 uz,
6754 vx,
6755 vy,
6756 vz,
6757 mr,
6758 nc,
6759 nr,
6760 fr,
6761 hue,
6762 );
6763 return Ok(Value::Unit);
6764 },
6765
6766 "vtex_halftone" | "ลายจุด" | "纹半调" | "網点模様" | "망점" | "الگوی_نیمتن" | "نقش_نصفي" | "דוגמת_חצי_גוון" | "نقش_ہاف_ٹون" | "motif_demi_ton" | "muster_halbton" | "узор_растр" => {
6768 let cx = self.arg_num(&args, 0, 0.)? as f32;
6769 let cy = self.arg_num(&args, 1, 0.)? as f32;
6770 let cz = self.arg_num(&args, 2, 0.)? as f32;
6771 let ux = self.arg_num(&args, 3, 1.)? as f32;
6772 let uy = self.arg_num(&args, 4, 0.)? as f32;
6773 let uz = self.arg_num(&args, 5, 0.)? as f32;
6774 let vx = self.arg_num(&args, 6, 0.)? as f32;
6775 let vy = self.arg_num(&args, 7, 0.)? as f32;
6776 let vz = self.arg_num(&args, 8, 1.)? as f32;
6777 let cols = self.arg_num(&args, 9, 16.)? as usize;
6778 let rows = self.arg_num(&args, 10, 12.)? as usize;
6779 let cw = self.arg_num(&args, 11, 0.5)? as f32;
6780 let ch = self.arg_num(&args, 12, 0.5)? as f32;
6781 let dens = self.arg_num(&args, 13, 0.4)? as f32;
6782 let fr = self.arg_num(&args, 14, 0.)? as f32;
6783 let hue = self.arg_num(&args, 15, 0.)? as f32;
6784 let mut gfx = self.gfx.borrow_mut();
6785 let cam = gfx.camera.clone();
6786 crate::gfx::vtex::draw_halftone(
6787 &mut gfx.depth_queue,
6788 &cam,
6789 cx,
6790 cy,
6791 cz,
6792 ux,
6793 uy,
6794 uz,
6795 vx,
6796 vy,
6797 vz,
6798 cols,
6799 rows,
6800 cw,
6801 ch,
6802 dens,
6803 fr,
6804 hue,
6805 );
6806 return Ok(Value::Unit);
6807 },
6808
6809 "vtex_tessellated" | "ลายตาข่าย" | "纹镶嵌" | "網目模様" | "격자망" | "الگوی_کاشیکاری" | "نقش_مرصوف_متكرر" | "דוגמת_ריצוף_חוזר" | "نقش_ٹائلنگ" | "motif_tesselle" | "muster_tessellation" | "узор_мозаика" =>
6811 {
6812 let cx = self.arg_num(&args, 0, 0.)? as f32;
6813 let cy = self.arg_num(&args, 1, 0.)? as f32;
6814 let cz = self.arg_num(&args, 2, 0.)? as f32;
6815 let ux = self.arg_num(&args, 3, 1.)? as f32;
6816 let uy = self.arg_num(&args, 4, 0.)? as f32;
6817 let uz = self.arg_num(&args, 5, 0.)? as f32;
6818 let vx = self.arg_num(&args, 6, 0.)? as f32;
6819 let vy = self.arg_num(&args, 7, 0.)? as f32;
6820 let vz = self.arg_num(&args, 8, 1.)? as f32;
6821 let cols = self.arg_num(&args, 9, 14.)? as usize;
6822 let rows = self.arg_num(&args, 10, 10.)? as usize;
6823 let cell = self.arg_num(&args, 11, 0.6)? as f32;
6824 let amp = self.arg_num(&args, 12, 0.25)? as f32;
6825 let freq = self.arg_num(&args, 13, 4.)? as f32;
6826 let fr = self.arg_num(&args, 14, 0.)? as f32;
6827 let hue = self.arg_num(&args, 15, 0.)? as f32;
6828 let mut gfx = self.gfx.borrow_mut();
6829 let cam = gfx.camera.clone();
6830 crate::gfx::vtex::draw_tessellated(
6831 &mut gfx.depth_queue,
6832 &cam,
6833 cx,
6834 cy,
6835 cz,
6836 ux,
6837 uy,
6838 uz,
6839 vx,
6840 vy,
6841 vz,
6842 cols,
6843 rows,
6844 cell,
6845 amp,
6846 freq,
6847 fr,
6848 hue,
6849 );
6850 return Ok(Value::Unit);
6851 },
6852
6853 "vtex_lotus" | "ลายดอกบัว" | "纹莲" | "蓮模様" | "연꽃무늬" | "الگوی_لوتوس" | "نقش_لوتس" | "דוגמת_לוטוס" | "نقش_کنول" | "motif_lotus" | "muster_lotus" | "узор_лотос" =>
6855 {
6856 let cx = self.arg_num(&args, 0, 0.)? as f32;
6857 let cy = self.arg_num(&args, 1, 0.)? as f32;
6858 let cz = self.arg_num(&args, 2, 0.)? as f32;
6859 let ux = self.arg_num(&args, 3, 1.)? as f32;
6860 let uy = self.arg_num(&args, 4, 0.)? as f32;
6861 let uz = self.arg_num(&args, 5, 0.)? as f32;
6862 let vx = self.arg_num(&args, 6, 0.)? as f32;
6863 let vy = self.arg_num(&args, 7, 0.)? as f32;
6864 let vz = self.arg_num(&args, 8, 1.)? as f32;
6865 let ri = self.arg_num(&args, 9, 1.)? as f32;
6866 let ro = self.arg_num(&args, 10, 2.)? as f32;
6867 let np = self.arg_num(&args, 11, 12.)? as usize;
6868 let fr = self.arg_num(&args, 12, 0.)? as f32;
6869 let hue = self.arg_num(&args, 13, 0.)? as f32;
6870 let mut gfx = self.gfx.borrow_mut();
6871 let cam = gfx.camera.clone();
6872 crate::gfx::vtex::draw_lotus(
6873 &mut gfx.depth_queue,
6874 &cam,
6875 cx,
6876 cy,
6877 cz,
6878 ux,
6879 uy,
6880 uz,
6881 vx,
6882 vy,
6883 vz,
6884 ri,
6885 ro,
6886 np,
6887 fr,
6888 hue,
6889 );
6890 return Ok(Value::Unit);
6891 },
6892
6893 "vtex_chakra" | "ลายจักร" | "纹轮" | "輪模様" | "바퀴무늬" | "الگوی_چاکرا" | "نقش_تشاكرا" | "דוגמת_צ'אקרה" | "نقش_چکر" | "motif_chakra" | "muster_chakra" | "узор_чакра" => {
6895 let cx = self.arg_num(&args, 0, 0.)? as f32;
6896 let cy = self.arg_num(&args, 1, 0.)? as f32;
6897 let cz = self.arg_num(&args, 2, 0.)? as f32;
6898 let ux = self.arg_num(&args, 3, 1.)? as f32;
6899 let uy = self.arg_num(&args, 4, 0.)? as f32;
6900 let uz = self.arg_num(&args, 5, 0.)? as f32;
6901 let vx = self.arg_num(&args, 6, 0.)? as f32;
6902 let vy = self.arg_num(&args, 7, 0.)? as f32;
6903 let vz = self.arg_num(&args, 8, 1.)? as f32;
6904 let r = self.arg_num(&args, 9, 2.)? as f32;
6905 let ns = self.arg_num(&args, 10, 8.)? as usize;
6906 let fr = self.arg_num(&args, 11, 0.)? as f32;
6907 let hue = self.arg_num(&args, 12, 0.)? as f32;
6908 let mut gfx = self.gfx.borrow_mut();
6909 let cam = gfx.camera.clone();
6910 crate::gfx::vtex::draw_chakra(
6911 &mut gfx.depth_queue,
6912 &cam,
6913 cx,
6914 cy,
6915 cz,
6916 ux,
6917 uy,
6918 uz,
6919 vx,
6920 vy,
6921 vz,
6922 r,
6923 ns,
6924 fr,
6925 hue,
6926 );
6927 return Ok(Value::Unit);
6928 },
6929
6930 "vtex_yantra" | "ลายยันต์" | "纹咒" | "護符模様" | "부적무늬" | "الگوی_یانترا" | "نقش_يانترا" | "דוגמת_יאנטרה" | "نقش_ینترا" | "motif_yantra" | "muster_yantra" | "узор_янтра" =>
6932 {
6933 let cx = self.arg_num(&args, 0, 0.)? as f32;
6934 let cy = self.arg_num(&args, 1, 0.)? as f32;
6935 let cz = self.arg_num(&args, 2, 0.)? as f32;
6936 let ux = self.arg_num(&args, 3, 1.)? as f32;
6937 let uy = self.arg_num(&args, 4, 0.)? as f32;
6938 let uz = self.arg_num(&args, 5, 0.)? as f32;
6939 let vx = self.arg_num(&args, 6, 0.)? as f32;
6940 let vy = self.arg_num(&args, 7, 0.)? as f32;
6941 let vz = self.arg_num(&args, 8, 1.)? as f32;
6942 let nl = self.arg_num(&args, 9, 4.)? as usize;
6943 let mr = self.arg_num(&args, 10, 3.)? as f32;
6944 let fr = self.arg_num(&args, 11, 0.)? as f32;
6945 let hue = self.arg_num(&args, 12, 0.)? as f32;
6946 let mut gfx = self.gfx.borrow_mut();
6947 let cam = gfx.camera.clone();
6948 crate::gfx::vtex::draw_yantra(
6949 &mut gfx.depth_queue,
6950 &cam,
6951 cx,
6952 cy,
6953 cz,
6954 ux,
6955 uy,
6956 uz,
6957 vx,
6958 vy,
6959 vz,
6960 nl,
6961 mr,
6962 fr,
6963 hue,
6964 );
6965 return Ok(Value::Unit);
6966 },
6967
6968 "vtex_spiked_cog" | "ฟันเฟืองหนาม" | "纹棘轮" | "歯車模様" | "톱니바퀴" | "الگوی_چرخدنده_خاردار" | "نقش_ترس_شائك" | "דוגמת_גלגל_קוצני" | "نقش_خاردار_گیئر" | "motif_engrenage_pointes" | "muster_stachelzahnrad" | "узор_шестерня_шипы" =>
6970 {
6971 let cx = self.arg_num(&args, 0, 0.)? as f32;
6972 let cy = self.arg_num(&args, 1, 0.)? as f32;
6973 let cz = self.arg_num(&args, 2, 0.)? as f32;
6974 let ux = self.arg_num(&args, 3, 1.)? as f32;
6975 let uy = self.arg_num(&args, 4, 0.)? as f32;
6976 let uz = self.arg_num(&args, 5, 0.)? as f32;
6977 let vx = self.arg_num(&args, 6, 0.)? as f32;
6978 let vy = self.arg_num(&args, 7, 0.)? as f32;
6979 let vz = self.arg_num(&args, 8, 1.)? as f32;
6980 let nt = self.arg_num(&args, 9, 12.)? as usize;
6981 let rb = self.arg_num(&args, 10, 1.)? as f32;
6982 let rs = self.arg_num(&args, 11, 1.3)? as f32;
6983 let rh = self.arg_num(&args, 12, 0.2)? as f32;
6984 let ns = self.arg_num(&args, 13, 6.)? as usize;
6985 let fr = self.arg_num(&args, 14, 0.)? as f32;
6986 let hue = self.arg_num(&args, 15, 0.)? as f32;
6987 let mut gfx = self.gfx.borrow_mut();
6988 let cam = gfx.camera.clone();
6989 crate::gfx::vtex::draw_spiked_cog(
6990 &mut gfx.depth_queue,
6991 &cam,
6992 cx,
6993 cy,
6994 cz,
6995 ux,
6996 uy,
6997 uz,
6998 vx,
6999 vy,
7000 vz,
7001 nt,
7002 rb,
7003 rs,
7004 rh,
7005 ns,
7006 fr,
7007 hue,
7008 );
7009 return Ok(Value::Unit);
7010 },
7011
7012 "vtex_torii" | "ประตูโทริอิ" | "纹鸟居" | "鳥居" | "도리이" | "الگوی_توری_ژاپنی" | "نقش_توري" | "דוגמת_טוריי" | "نقش_توری_گیٹ" | "motif_torii" | "muster_torii" | "узор_тории" =>
7014 {
7015 let cx = self.arg_num(&args, 0, 0.)? as f32;
7016 let cy = self.arg_num(&args, 1, 0.)? as f32;
7017 let cz = self.arg_num(&args, 2, 0.)? as f32;
7018 let ux = self.arg_num(&args, 3, 1.)? as f32;
7019 let uy = self.arg_num(&args, 4, 0.)? as f32;
7020 let uz = self.arg_num(&args, 5, 0.)? as f32;
7021 let vx = self.arg_num(&args, 6, 0.)? as f32;
7022 let vy = self.arg_num(&args, 7, 0.)? as f32;
7023 let vz = self.arg_num(&args, 8, 1.)? as f32;
7024 let w = self.arg_num(&args, 9, 4.)? as f32;
7025 let h = self.arg_num(&args, 10, 5.)? as f32;
7026 let fr = self.arg_num(&args, 11, 0.)? as f32;
7027 let hue = self.arg_num(&args, 12, 0.)? as f32;
7028 let mut gfx = self.gfx.borrow_mut();
7029 let cam = gfx.camera.clone();
7030 crate::gfx::vtex::draw_torii(
7031 &mut gfx.depth_queue,
7032 &cam,
7033 cx,
7034 cy,
7035 cz,
7036 ux,
7037 uy,
7038 uz,
7039 vx,
7040 vy,
7041 vz,
7042 w,
7043 h,
7044 fr,
7045 hue,
7046 );
7047 return Ok(Value::Unit);
7048 },
7049
7050 "vtex_pagoda" | "เจดีย์" | "纹塔" | "塔" | "탑" | "الگوی_پاگودا" | "نقش_باغودا" | "דוגמת_פגודה" | "نقش_پگوڈا" | "motif_pagode" | "muster_pagode" | "узор_пагода" => {
7052 let cx = self.arg_num(&args, 0, 0.)? as f32;
7053 let cy = self.arg_num(&args, 1, 0.)? as f32;
7054 let cz = self.arg_num(&args, 2, 0.)? as f32;
7055 let ux = self.arg_num(&args, 3, 1.)? as f32;
7056 let uy = self.arg_num(&args, 4, 0.)? as f32;
7057 let uz = self.arg_num(&args, 5, 0.)? as f32;
7058 let vx = self.arg_num(&args, 6, 0.)? as f32;
7059 let vy = self.arg_num(&args, 7, 0.)? as f32;
7060 let vz = self.arg_num(&args, 8, 1.)? as f32;
7061 let nt = self.arg_num(&args, 9, 5.)? as usize;
7062 let bw = self.arg_num(&args, 10, 2.)? as f32;
7063 let th = self.arg_num(&args, 11, 1.)? as f32;
7064 let tp = self.arg_num(&args, 12, 0.72)? as f32;
7065 let eo = self.arg_num(&args, 13, 0.28)? as f32;
7066 let fr = self.arg_num(&args, 14, 0.)? as f32;
7067 let hue = self.arg_num(&args, 15, 0.)? as f32;
7068 let mut gfx = self.gfx.borrow_mut();
7069 let cam = gfx.camera.clone();
7070 crate::gfx::vtex::draw_pagoda(
7071 &mut gfx.depth_queue,
7072 &cam,
7073 cx,
7074 cy,
7075 cz,
7076 ux,
7077 uy,
7078 uz,
7079 vx,
7080 vy,
7081 vz,
7082 nt,
7083 bw,
7084 th,
7085 tp,
7086 eo,
7087 fr,
7088 hue,
7089 );
7090 return Ok(Value::Unit);
7091 },
7092
7093 #[cfg(not(target_arch = "wasm32"))]
7099 "audio_tone"
7100 | "เสียงโทน"
7101 | "音调"
7102 | "音調"
7103 | "음조"
7104 | "空间音"
7105 | "空間音"
7106 | "공간음" | "تن_صدا" | "نغمة" | "צליל" | "آواز_کا_سر" | "tonalité_audio" | "audioton" | "звук_тон" => {
7107 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7108 let x = self.arg_num(&args, 1, 0.0)? as f32;
7109 let y = self.arg_num(&args, 2, 0.0)? as f32;
7110 let z = self.arg_num(&args, 3, 0.0)? as f32;
7111 let w = self.arg_num(&args, 4, 1.0)? as f32;
7112 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7113 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7114 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7115 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7116 if let Some(audio) = &self.audio {
7117 audio.set_tone(
7118 idx,
7119 ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth },
7120 );
7121 }
7122 return Ok(Value::Unit);
7123 },
7124
7125 #[cfg(not(target_arch = "wasm32"))]
7126 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" | "شنونده_صدا" | "مستمع_الصوت" | "מאזין_קול" | "آواز_سننے_والا" | "auditeur_audio" | "audiohörer" | "звук_слушатель" =>
7127 {
7128 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7129 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7130 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7131 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7132 if let Some(audio) = &self.audio {
7133 audio.set_listener(cry, sry, crx, srx);
7134 }
7135 return Ok(Value::Unit);
7136 },
7137
7138 #[cfg(not(target_arch = "wasm32"))]
7139 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" | "موسیقی_پسزمینه" | "موسيقى_خلفية" | "מוזיקת_רקע" | "پس_منظر_موسیقی" | "musique_fond" | "hintergrundmusik" | "фоновая_музыка" =>
7140 {
7141 let path = match args.first() {
7142 Some(Value::Str(s)) => s.clone(),
7143 _ => return Ok(Value::Unit),
7144 };
7145 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7146 if let Some(audio) = &self.audio {
7147 audio.load_bgm(&path, vol);
7148 }
7149 return Ok(Value::Unit);
7150 },
7151
7152 #[cfg(not(target_arch = "wasm32"))]
7153 "audio_bgm_volume"
7154 | "ระดับเสียงพื้นหลัง"
7155 | "ระดับเพลงประกอบ"
7156 | "背景乐音量"
7157 | "BGM音量"
7158 | "배경음악음량" | "بلندی_موسیقی_پسزمینه" | "مستوى_موسيقى_الخلفية" | "עוצמת_מוזיקת_רקע" | "پس_منظر_موسیقی_شدت" | "volume_musique_fond" | "hintergrundmusiklautstärke" | "громкость_фоновой_музыки" => {
7159 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7160 if let Some(audio) = &self.audio {
7161 audio.set_bgm_volume(vol);
7162 }
7163 return Ok(Value::Unit);
7164 },
7165
7166 #[cfg(not(target_arch = "wasm32"))]
7167 "audio_volume" | "ระดับเสียง" | "音量" | "음량" | "بلندی_صدا" | "مستوى_الصوت" | "עוצמת_קול" | "آواز_کی_شدت" | "volume_audio" | "audiolautstärke" | "звук_громкость" => {
7168 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7169 if let Some(audio) = &self.audio {
7170 audio.set_master_volume(vol);
7171 }
7172 return Ok(Value::Unit);
7173 },
7174
7175 #[cfg(target_arch = "wasm32")]
7177 "audio_tone"
7178 | "เสียงโทน"
7179 | "音调"
7180 | "音調"
7181 | "음조"
7182 | "空间音"
7183 | "空間音"
7184 | "공간음" | "تن_صدا" | "نغمة" | "צליל" | "آواز_کا_سر" | "tonalité_audio" | "audioton" | "звук_тон" => {
7185 let idx = self.arg_num(&args, 0, 0.0)? as usize;
7186 let x = self.arg_num(&args, 1, 0.0)? as f32;
7187 let y = self.arg_num(&args, 2, 0.0)? as f32;
7188 let z = self.arg_num(&args, 3, 0.0)? as f32;
7189 let w = self.arg_num(&args, 4, 1.0)? as f32;
7190 let freq = self.arg_num(&args, 5, 220.0)? as f32;
7191 let amp = self.arg_num(&args, 6, 0.15)? as f32;
7192 let lfo_rate = self.arg_num(&args, 7, 0.5)? as f32;
7193 let lfo_depth = self.arg_num(&args, 8, 0.02)? as f32;
7194 crate::gfx::audio_web::set_tone(idx, x, y, z, w, freq, amp, lfo_rate, lfo_depth);
7195 return Ok(Value::Unit);
7196 },
7197
7198 #[cfg(target_arch = "wasm32")]
7199 "audio_listener" | "ผู้ฟัง" | "音频监听" | "音声リスナー" | "오디오리스너" | "شنونده_صدا" | "مستمع_الصوت" | "מאזין_קול" | "آواز_سننے_والا" | "auditeur_audio" | "audiohörer" | "звук_слушатель" =>
7200 {
7201 let cry = self.arg_num(&args, 0, 1.0)? as f32;
7202 let sry = self.arg_num(&args, 1, 0.0)? as f32;
7203 let crx = self.arg_num(&args, 2, 1.0)? as f32;
7204 let srx = self.arg_num(&args, 3, 0.0)? as f32;
7205 crate::gfx::audio_web::set_listener(cry, sry, crx, srx);
7206 return Ok(Value::Unit);
7207 },
7208
7209 #[cfg(target_arch = "wasm32")]
7210 "audio_bgm" | "เพลงพื้นหลัง" | "เพลงประกอบ" | "背景乐" | "BGM" | "배경음악" | "موسیقی_پسزمینه" | "موسيقى_خلفية" | "מוזיקת_רקע" | "پس_منظر_موسیقی" | "musique_fond" | "hintergrundmusik" | "фоновая_музыка" =>
7211 {
7212 let path = self.arg_str(&args, 0, "");
7213 let vol = self.arg_num(&args, 1, 0.5)? as f32;
7214 crate::gfx::audio_web::load_bgm(&path, vol);
7215 return Ok(Value::Unit);
7216 },
7217
7218 #[cfg(target_arch = "wasm32")]
7219 "audio_bgm_volume"
7220 | "ระดับเสียงพื้นหลัง"
7221 | "ระดับเพลงประกอบ"
7222 | "背景乐音量"
7223 | "BGM音量"
7224 | "배경음악음량" | "بلندی_موسیقی_پسزمینه" | "مستوى_موسيقى_الخلفية" | "עוצמת_מוזיקת_רקע" | "پس_منظر_موسیقی_شدت" | "volume_musique_fond" | "hintergrundmusiklautstärke" | "громкость_фоновой_музыки" => {
7225 let vol = self.arg_num(&args, 0, 0.5)? as f32;
7226 crate::gfx::audio_web::set_bgm_volume(vol);
7227 return Ok(Value::Unit);
7228 },
7229
7230 #[cfg(target_arch = "wasm32")]
7231 "audio_volume" | "ระดับเสียง" | "音量" | "음량" | "بلندی_صدا" | "مستوى_الصوت" | "עוצמת_קול" | "آواز_کی_شدت" | "volume_audio" | "audiolautstärke" | "звук_громкость" => {
7232 let vol = self.arg_num(&args, 0, 0.7)? as f32;
7233 crate::gfx::audio_web::set_master_volume(vol);
7234 return Ok(Value::Unit);
7235 },
7236
7237 #[cfg(target_arch = "wasm32")]
7239 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" | "بارگذاری_نمونه_صدا" | "تحميل_عينة_صوتية" | "טעינת_דגימת_קול" | "آواز_نمونہ_لوڈ" | "charger_échantillon" | "sample_laden" | "загрузить_семпл" =>
7240 {
7241 let path = self.arg_str(&args, 0, "");
7242 let resolved = self.wasm_resolve_source_path(&path);
7243 match wasm_fetch_bytes(&resolved)
7244 .and_then(|bytes| ling_music::from_bytes(&bytes).map_err(|e| e.to_string()))
7245 {
7246 Ok(t) => {
7247 let id = crate::gfx::audio_web::add_sample(&t.stereo, t.channels, t.rate);
7248 return Ok(Value::Number(id as f64));
7249 },
7250 Err(e) => {
7251 eprintln!("audio_sample_load failed ({path}): {e}");
7252 return Ok(Value::Number(-1.0));
7253 },
7254 }
7255 },
7256 #[cfg(target_arch = "wasm32")]
7257 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" | "پخش_نمونه_صدا" | "تشغيل_عينة_صوتية" | "נגינת_דגימת_קול" | "آواز_نمونہ_چلاؤ" | "jouer_échantillon" | "sample_abspielen" | "играть_семпл" =>
7258 {
7259 let id = self.arg_num(&args, 0, 0.0)? as usize;
7260 let x = self.arg_num(&args, 1, 0.0)? as f32;
7261 let y = self.arg_num(&args, 2, 0.0)? as f32;
7262 let z = self.arg_num(&args, 3, 0.0)? as f32;
7263 let vol = self.arg_num(&args, 5, 1.0)? as f32;
7265 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
7266 crate::gfx::audio_web::play_sample(id, x, y, z, vol, looping);
7267 return Ok(Value::Number(0.0));
7268 },
7269 #[cfg(target_arch = "wasm32")]
7270 "audio_sample_stop"
7271 | "停止采样"
7272 | "サンプル停止"
7273 | "샘플정지"
7274 | "หยุดตัวอย่างเสียง"
7275 | "audio_fx_reverb"
7276 | "混响"
7277 | "リバーブ"
7278 | "리버브"
7279 | "เสียงก้อง"
7280 | "audio_fx_delay"
7281 | "回声"
7282 | "ディレイ効果"
7283 | "딜레이"
7284 | "เสียงสะท้อน"
7285 | "audio_fx_lowpass"
7286 | "低通滤波"
7287 | "ローパス"
7288 | "저역통과"
7289 | "กรองความถี่ต่ำ" | "توقف_نمونه_صدا" | "إيقاف_عينة_صوتية" | "עצירת_דגימת_קול" | "آواز_نمونہ_روکو" | "arrêter_échantillon" | "sample_stoppen" | "остановить_семпл" => {
7290 return Ok(Value::Unit);
7291 },
7292
7293 "รอหน้าต่าง" | "wait_window" | "gfx_wait" | "انتظار_پنجره" | "انتظر_النافذة" | "המתן_לחלון" | "ونڈو_انتظار" => {
7295 #[cfg(not(target_arch = "wasm32"))]
7296 loop {
7297 let still_open = {
7298 let gfx = self.gfx.borrow();
7299 gfx.window
7300 .as_ref()
7301 .map(|w| w.is_open() && !w.is_key_down(minifb::Key::Escape))
7302 .unwrap_or(false)
7303 };
7304 if !still_open {
7305 break;
7306 }
7307 let (buf, w, h) = {
7308 let gfx = self.gfx.borrow();
7309 (gfx.buffer.clone(), gfx.width, gfx.height)
7310 };
7311 let mut gfx = self.gfx.borrow_mut();
7312 if let Some(win) = gfx.window.as_mut() {
7313 if win.update_with_buffer(&buf, w, h).is_err() {
7314 break;
7315 }
7316 }
7317 }
7318 return Ok(Value::Unit);
7319 },
7320
7321 "read_file" | "อ่านไฟล์" | "خواندن_فایل" | "اقرأ_الملف" | "קרא_קובץ" | "فائل_پڑھو" => {
7323 #[cfg(target_arch = "wasm32")]
7324 return Ok(Value::Str(String::new()));
7325 #[cfg(not(target_arch = "wasm32"))]
7326 {
7327 let path = self.arg_str(&args, 0, "");
7328 return std::fs::read_to_string(&path)
7329 .map(Value::Str)
7330 .map_err(|e| EvalErr::from(format!("read_file '{path}': {e}")));
7331 }
7332 },
7333 #[cfg(not(target_arch = "wasm32"))]
7335 "net_host" | "เน็ตโฮสต์" | "میزبانی_شبکه" | "استضف_الشبكة" | "ארח_רשת" | "نیٹ_ہوسٹ" => {
7336 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7337 net::host(port);
7338 return Ok(Value::Unit);
7339 },
7340 #[cfg(not(target_arch = "wasm32"))]
7341 "net_join" | "เน็ตจอย" | "پیوستن_شبکه" | "انضم_للشبكة" | "הצטרף_לרשת" | "نیٹ_شمولیت" => {
7342 let ip = self.arg_str(&args, 0, "127.0.0.1");
7343 let port = self.arg_num(&args, 1, 7777.0)? as u16;
7344 net::join(&ip, port);
7345 return Ok(Value::Unit);
7346 },
7347 #[cfg(not(target_arch = "wasm32"))]
7348 "net_send" | "เน็ตส่ง" | "ارسال_شبکه" | "أرسل_عبر_الشبكة" | "שלח_ברשת" | "نیٹ_بھیجو" => {
7349 let s = self.arg_str(&args, 0, "");
7350 net::send(&s);
7351 return Ok(Value::Unit);
7352 },
7353 #[cfg(not(target_arch = "wasm32"))]
7354 "net_recv" | "เน็ตรับ" | "دریافت_شبکه" | "استقبل_من_الشبكة" | "קבל_מרשת" | "نیٹ_وصول" => {
7355 return Ok(Value::Str(net::recv()));
7356 },
7357 #[cfg(not(target_arch = "wasm32"))]
7358 "net_status" | "เน็ตสถานะ" | "وضعیت_شبکه" | "حالة_الشبكة" | "סטטוס_רשת" | "نیٹ_حالت" => {
7359 return Ok(Value::Number(net::status() as f64));
7360 },
7361 #[cfg(not(target_arch = "wasm32"))]
7362 "net_recv_from" => {
7363 return Ok(Value::Str(net::recv_from()));
7364 },
7365 #[cfg(not(target_arch = "wasm32"))]
7366 "net_send_to" => {
7367 let id = self.arg_num(&args, 0, 0.0)? as u64;
7368 let s = self.arg_str(&args, 1, "");
7369 net::send_to(id, &s);
7370 return Ok(Value::Unit);
7371 },
7372 #[cfg(not(target_arch = "wasm32"))]
7373 "net_close" | "연결종료" => {
7374 net::close();
7375 return Ok(Value::Unit);
7376 },
7377 #[cfg(not(target_arch = "wasm32"))]
7379 "net_announce" | "เน็ตประกาศ" | "اعلام_شبکه" | "أعلن_في_الشبكة" | "הכרז_ברשת" | "نیٹ_اعلان" => {
7380 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7381 let info = self.arg_str(&args, 1, "");
7382 net::announce(port, &info);
7383 return Ok(Value::Unit);
7384 },
7385 #[cfg(not(target_arch = "wasm32"))]
7386 "net_announce_stop" | "เน็ตหยุดประกาศ" | "توقف_اعلام" | "أوقف_الإعلان" | "עצור_הכרזה" | "اعلان_روکو" => {
7387 net::announce_stop();
7388 return Ok(Value::Unit);
7389 },
7390 #[cfg(not(target_arch = "wasm32"))]
7391 "net_discover" | "เน็ตค้นหา" | "کشف_شبکه" | "اكتشف_الشبكة" | "גלה_רשת" | "نیٹ_دریافت" => {
7392 let port = self.arg_num(&args, 0, 7778.0)? as u16;
7393 return Ok(Value::Str(net::discover(port)));
7394 },
7395 #[cfg(not(target_arch = "wasm32"))]
7396 "net_test" | "เน็ตทดสอบ" | "آزمون_شبکه" | "اختبر_الشبكة" | "בדוק_רשת" | "نیٹ_ٹیسٹ" => {
7397 let port = self.arg_num(&args, 0, 7777.0)? as u16;
7398 return Ok(Value::Str(net::test_bind(port)));
7399 },
7400 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7402 "http_route" | "เว็บเส้นทาง" | "مسیر_HTTP" | "مسار_HTTP" | "נתיב_HTTP" | "HTTP_روٹ" => {
7403 let method = self.arg_str(&args, 0, "GET").to_uppercase();
7404 let path = self.arg_str(&args, 1, "/");
7405 let handler = args.get(2).cloned().unwrap_or(Value::Unit);
7406 self.http_routes.push((method, path, handler));
7407 return Ok(Value::Unit);
7408 },
7409 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7413 "http_static" | "เว็บสแตติก" | "فایل_ایستای_HTTP" | "ملفات_HTTP_ثابتة" | "קבצים_סטטיים_HTTP" | "HTTP_مستقل_فائل" => {
7414 let prefix = self.arg_str(&args, 0, "/static");
7415 let dir = self.arg_str(&args, 1, "static");
7416 self.http_static_dirs.push((prefix, dir));
7417 return Ok(Value::Unit);
7418 },
7419 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7420 "http_serve" | "เว็บเสิร์ฟ" | "سرویس_HTTP" | "قدّم_HTTP" | "הגש_HTTP" | "HTTP_سرو" => {
7421 let host = self.arg_str(&args, 0, "127.0.0.1");
7422 let port = self.arg_num(&args, 1, 8080.0)? as u16;
7423 let routes = std::mem::take(&mut self.http_routes);
7424 let static_dirs = std::mem::take(&mut self.http_static_dirs);
7425 let rx = web::spawn_server(host.clone(), port, static_dirs);
7431 for pending in rx {
7432 let matched = routes
7433 .iter()
7434 .find(|(m, p, _)| m == &pending.method && p == &pending.path);
7435 let response = match matched {
7436 Some((_, _, handler)) => {
7437 let req_value = Value::Struct {
7438 name: "Request".to_string(),
7439 fields: vec![
7440 ("method".to_string(), Value::Str(pending.method.clone())),
7441 ("path".to_string(), Value::Str(pending.path.clone())),
7442 ("query".to_string(), Value::Str(pending.query.clone())),
7443 ("body".to_string(), Value::Str(pending.body.clone())),
7444 ("cookie".to_string(), Value::Str(pending.cookie.clone())),
7445 (
7446 "authorization".to_string(),
7447 Value::Str(pending.authorization.clone()),
7448 ),
7449 (
7450 "client_ip".to_string(),
7451 Value::Str(pending.client_ip.clone()),
7452 ),
7453 ],
7454 };
7455 match self.call_value(handler.clone(), vec![req_value]) {
7456 Ok(v) => web::value_to_response(&v),
7457 Err(e) => web::HttpResponse {
7458 status: 500,
7459 content_type: "text/plain; charset=utf-8".to_string(),
7460 body: format!("handler error: {e:?}"),
7461 set_cookie: None,
7462 location: None,
7463 },
7464 }
7465 },
7466 None => web::HttpResponse {
7467 status: 404,
7468 content_type: "text/plain; charset=utf-8".to_string(),
7469 body: "not found".to_string(),
7470 set_cookie: None,
7471 location: None,
7472 },
7473 };
7474 let _ = pending.respond_to.send(response);
7475 }
7476 return Ok(Value::Unit);
7477 },
7478 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7482 "http_post_async" | "เว็บโพสต์ไม่บล็อก" | "ارسال_ناهمگام_HTTP" | "أرسل_HTTP_غير_متزامن" | "שלח_HTTP_אסינכרוני" | "HTTP_غیر_ہمزمان_بھیجو" => {
7483 let url = self.arg_str(&args, 0, "");
7484 let body = self.arg_str(&args, 1, "");
7485 let content_type = self.arg_str(&args, 2, "application/json");
7486 let id = self.async_jobs.start_post(url, content_type, body);
7487 return Ok(Value::Str(id));
7488 },
7489 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7493 "http_job_poll" | "เว็บงานสำรวจ" | "بررسی_وظیفه_HTTP" | "استطلع_مهمة_HTTP" | "בדוק_משימת_HTTP" | "HTTP_کام_پول" => {
7494 let id = self.arg_str(&args, 0, "");
7495 return Ok(Value::Str(self.async_jobs.poll(&id).unwrap_or_default()));
7496 },
7497 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7504 "sdai_generate_start" | "เอสดีเอไอเริ่มสร้าง" | "شروع_تولید_هوش" | "ابدأ_توليد_الذكاء" | "התחל_יצירת_בינה" | "اے_آئی_تخلیق_شروع" => {
7505 let base_url = self.arg_str(&args, 0, "http://127.0.0.1:1342");
7506 let prompt = self.arg_str(&args, 1, "");
7507 let width = self.arg_num(&args, 2, 512.0)? as u32;
7508 let height = self.arg_num(&args, 3, 512.0)? as u32;
7509 let id = self.async_jobs.start_sdai_txt2img(base_url, prompt, width, height);
7510 return Ok(Value::Str(id));
7511 },
7512 "query_param" | "พารามิเตอร์" | "پارامتر_پرسوجو" | "معامل_الاستعلام" | "פרמטר_שאילתה" | "کوئری_پیرامیٹر" => {
7514 let qs = self.arg_str(&args, 0, "");
7515 let name = self.arg_str(&args, 1, "");
7516 let default = self.arg_str(&args, 2, "");
7517 let mut found = default;
7518 for pair in qs.split('&') {
7519 let mut it = pair.splitn(2, '=');
7520 if it.next().unwrap_or("") == name {
7521 found = url_decode(it.next().unwrap_or(""));
7522 break;
7523 }
7524 }
7525 return Ok(Value::Str(found));
7526 },
7527 "cookie_get" | "รับคุกกี้" | "دریافت_کوکی" | "اجلب_الكعكة" | "קבל_עוגייה" | "کوکی_حاصل_کرو" => {
7529 let header = self.arg_str(&args, 0, "");
7530 let name = self.arg_str(&args, 1, "");
7531 let default = self.arg_str(&args, 2, "");
7532 let mut found = default;
7533 for pair in header.split(';') {
7534 let p = pair.trim();
7535 let mut it = p.splitn(2, '=');
7536 if it.next().unwrap_or("") == name {
7537 found = it.next().unwrap_or("").to_string();
7538 break;
7539 }
7540 }
7541 return Ok(Value::Str(found));
7542 },
7543 "html_escape" | "กันเอชทีเอ็มแอล" | "فرار_HTML" | "أفلت_HTML" | "בריחת_HTML" | "HTML_ایسکیپ" => {
7545 let s = self.arg_str(&args, 0, "");
7546 return Ok(Value::Str(
7547 s.replace('&', "&")
7548 .replace('<', "<")
7549 .replace('>', ">")
7550 .replace('"', """)
7551 .replace('\'', "'"),
7552 ));
7553 },
7554 "json_escape" | "หนีเจสัน" | "فرار_JSON" | "أفلت_JSON" | "בריחת_JSON" | "JSON_ایسکیپ" => {
7559 let s = self.arg_str(&args, 0, "");
7560 let mut out = String::with_capacity(s.len() + 8);
7561 for c in s.chars() {
7562 match c {
7563 '"' => out.push_str("\\\""),
7564 '\\' => out.push_str("\\\\"),
7565 '\n' => out.push_str("\\n"),
7566 '\r' => out.push_str("\\r"),
7567 '\t' => out.push_str("\\t"),
7568 c if (c as u32) < 0x20 => {
7569 out.push_str(&format!("\\u{:04x}", c as u32))
7570 },
7571 c => out.push(c),
7572 }
7573 }
7574 return Ok(Value::Str(out));
7575 },
7576 "cli_arg" | "อาร์กิวเมนต์" | "آرگومان_خطفرمان" | "معامل_سطر_الأوامر" | "ארגומנט_שורת_פקודה" | "سی_ایل_آئی_دلیل" => {
7578 let name = self.arg_str(&args, 0, "");
7579 let default = self.arg_str(&args, 1, "");
7580 let flag = format!("--{name}");
7581 let argv: Vec<String> = std::env::args().collect();
7582 let found = argv
7583 .iter()
7584 .position(|a| a == &flag)
7585 .and_then(|i| argv.get(i + 1))
7586 .cloned()
7587 .unwrap_or(default);
7588 return Ok(Value::Str(found));
7589 },
7590 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7592 "db_open" | "ฐานข้อมูลเปิด" | "باز_کردن_پایگاه_داده" | "افتح_قاعدة_البيانات" | "פתח_מסד_נתונים" | "ڈیٹا_بیس_کھولو" => {
7593 let path = self.arg_str(&args, 0, "app.db");
7594 let conn = ling_http::rusqlite::Connection::open(&path)
7595 .map_err(|e| EvalErr::from(format!("db_open '{path}': {e}")))?;
7596 let _ = conn.execute_batch("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;");
7597 self.db = Some(conn);
7598 return Ok(Value::Unit);
7599 },
7600 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7603 "db_exec" | "ฐานข้อมูลรัน" | "اجرای_پایگاه_داده" | "نفّذ_في_قاعدة_البيانات" | "בצע_במסד_נתונים" | "ڈیٹا_بیس_عمل" => {
7604 let sql = self.arg_str(&args, 0, "");
7605 let params = values_to_sql_params(&args[1.min(args.len())..]);
7606 let conn = self
7607 .db
7608 .as_ref()
7609 .ok_or_else(|| EvalErr::from("db_exec: call db_open first".to_string()))?;
7610 let n = conn
7611 .execute(
7612 &sql,
7613 ling_http::rusqlite::params_from_iter(params.iter()),
7614 )
7615 .map_err(|e| EvalErr::from(format!("db_exec: {e}\n sql: {sql}")))?;
7616 return Ok(Value::Number(n as f64));
7617 },
7618 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
7620 "db_query" | "ฐานข้อมูลถาม" | "پرسوجوی_پایگاه_داده" | "استعلم_قاعدة_البيانات" | "שאילתת_מסד_נתונים" | "ڈیٹا_بیس_سوال" => {
7621 let sql = self.arg_str(&args, 0, "");
7622 let params = values_to_sql_params(&args[1.min(args.len())..]);
7623 let conn = self
7624 .db
7625 .as_ref()
7626 .ok_or_else(|| EvalErr::from("db_query: call db_open first".to_string()))?;
7627 let mut stmt = conn
7628 .prepare(&sql)
7629 .map_err(|e| EvalErr::from(format!("db_query: {e}\n sql: {sql}")))?;
7630 let col_names: Vec<String> =
7631 stmt.column_names().iter().map(|s| s.to_string()).collect();
7632 let mut rows = stmt
7633 .query(ling_http::rusqlite::params_from_iter(params.iter()))
7634 .map_err(|e| EvalErr::from(format!("db_query: {e}")))?;
7635 let mut out = Vec::new();
7636 while let Some(row) = rows
7637 .next()
7638 .map_err(|e| EvalErr::from(format!("db_query row: {e}")))?
7639 {
7640 let mut fields = Vec::with_capacity(col_names.len());
7641 for (i, col) in col_names.iter().enumerate() {
7642 use ling_http::rusqlite::types::ValueRef;
7643 let v = match row.get_ref(i) {
7644 Ok(ValueRef::Null) => Value::Str(String::new()),
7645 Ok(ValueRef::Integer(n)) => Value::Number(n as f64),
7646 Ok(ValueRef::Real(n)) => Value::Number(n),
7647 Ok(ValueRef::Text(t)) => {
7648 Value::Str(String::from_utf8_lossy(t).into_owned())
7649 },
7650 Ok(ValueRef::Blob(b)) =>
7651
7652 {
7653 use base64::Engine as _;
7654 Value::Str(base64::engine::general_purpose::STANDARD.encode(b))
7655 },
7656 Err(_) => Value::Str(String::new()),
7657 };
7658 fields.push((col.clone(), v));
7659 }
7660 out.push(Value::Struct { name: "Row".to_string(), fields });
7661 }
7662 return Ok(Value::List(Rc::new(out)));
7663 },
7664 #[cfg(not(target_arch = "wasm32"))]
7666 "gamepad_poll" | "จอยโพล" | "بررسی_دسته_بازی" | "استطلع_يد_اللعب" | "בדוק_בקר_משחק" | "گیم_پیڈ_پول_کرو" => {
7667 gamepad::poll();
7668 return Ok(Value::Unit);
7669 },
7670 #[cfg(not(target_arch = "wasm32"))]
7671 "gamepad_button" | "จอยปุ่ม" | "دکمه_دسته_بازی" | "زر_يد_اللعب" | "כפתור_בקר_משחק" | "گیم_پیڈ_بٹن" => {
7672 let name = self.arg_str(&args, 0, "");
7673 return Ok(Value::Number(if gamepad::button(&name) {
7674 1.0
7675 } else {
7676 0.0
7677 }));
7678 },
7679 #[cfg(not(target_arch = "wasm32"))]
7680 "gamepad_axis" | "จอยแกน" | "محور_دسته_بازی" | "محور_يد_اللعب" | "ציר_בקר_משחק" | "گیم_پیڈ_محور" => {
7681 let name = self.arg_str(&args, 0, "");
7682 return Ok(Value::Number(gamepad::axis(&name) as f64));
7683 },
7684 #[cfg(not(target_arch = "wasm32"))]
7685 "gamepad_rumble" | "จอยสั่น" | "لرزش_دسته_بازی" | "اهتزاز_يد_اللعب" | "רטט_בקר_משחק" | "گیم_پیڈ_تھرتھراہٹ" => {
7686 let low = self.arg_num(&args, 0, 0.0)? as f32;
7687 let high = self.arg_num(&args, 1, 0.0)? as f32;
7688 let ms = self.arg_num(&args, 2, 200.0)? as u32;
7689 gamepad::rumble(low, high, ms);
7690 return Ok(Value::Unit);
7691 },
7692 #[cfg(not(target_arch = "wasm32"))]
7693 "gamepad_list" | "จอยรายการ" | "فهرست_دستههای_بازی" | "قائمة_أيدي_اللعب" | "רשימת_בקרי_משחק" | "گیم_پیڈ_فہرست" => {
7694 return Ok(Value::Str(gamepad::list()));
7695 },
7696 #[cfg(not(target_arch = "wasm32"))]
7697 "gamepad_any" | "จอยใดๆ" | "هر_دسته_بازی" | "أي_يد_لعب" | "בקר_כלשהו" | "کوئی_بھی_گیم_پیڈ" => {
7698 return Ok(Value::Number(if gamepad::any_button() { 1.0 } else { 0.0 }));
7699 },
7700 #[cfg(target_arch = "wasm32")]
7702 "gamepad_poll" | "จอยโพล" | "gamepad_rumble" | "จอยสั่น" | "بررسی_دسته_بازی" | "استطلع_يد_اللعب" | "בדוק_בקר_משחק" | "گیم_پیڈ_پول_کرو" => {
7703 return Ok(Value::Unit);
7704 },
7705 #[cfg(target_arch = "wasm32")]
7706 "gamepad_button" | "จอยปุ่ม" | "gamepad_axis" | "จอยแกน" | "gamepad_any" | "จอยใดๆ" | "دکمه_دسته_بازی" | "زر_يد_اللعب" | "כפתור_בקר_משחק" | "گیم_پیڈ_بٹن" =>
7707 {
7708 return Ok(Value::Number(0.0));
7709 },
7710 #[cfg(target_arch = "wasm32")]
7711 "gamepad_list" | "จอยรายการ" | "فهرست_دستههای_بازی" | "قائمة_أيدي_اللعب" | "רשימת_בקרי_משחק" | "گیم_پیڈ_فہرست" => {
7712 return Ok(Value::Str(String::new()));
7713 },
7714
7715 #[cfg(not(target_arch = "wasm32"))]
7718 "nn_new" | "建神经网" | "ニューラル作成" | "신경망생성" | "สร้างโครงข่าย" | "شبکه_جدید" | "شبكة_جديدة" | "רשת_חדשה" | "نئی_نیورل_نیٹ" | "nouveau_réseau" | "neues_netz" | "новая_сеть" =>
7719 {
7720 let n_in = self.arg_num(&args, 0, 1.0)?.max(0.0) as usize;
7721 let seed = self.arg_num(&args, 1, 1.0)? as u64;
7722 return Ok(Value::Number(ai::nn_new(n_in, seed) as f64));
7723 },
7724 #[cfg(not(target_arch = "wasm32"))]
7726 "nn_dense" | "密集层" | "密層追加" | "밀집층" | "ชั้นหนาแน่น" | "لایه_متراکم" | "طبقة_كثيفة" | "שכבה_צפופה" | "ڈینس_لیئر" | "réseau_dense" | "netz_dicht" | "плотная_сеть" =>
7727 {
7728 let id = self.arg_num(&args, 0, -1.0)? as i64;
7729 let units = self.arg_num(&args, 1, 1.0)?.max(1.0) as usize;
7730 let act = self.arg_str(&args, 2, "relu");
7731 ai::nn_dense(id, units, &act);
7732 return Ok(Value::Unit);
7733 },
7734 #[cfg(not(target_arch = "wasm32"))]
7736 "nn_forward" | "神经前向" | "順伝播" | "순전파" | "ส่งต่อโครงข่าย" | "پیشروی_شبکه" | "تمرير_أمامي" | "העברה_קדימה" | "فارورڈ_پاس" | "propager_réseau" | "netz_vorwärts" | "прямой_проход_сети" =>
7737 {
7738 let id = self.arg_num(&args, 0, -1.0)? as i64;
7739 let input = self.arg_list_f32(&args, 1);
7740 let out = ai::nn_forward(id, &input);
7741 return Ok(Value::List(Rc::new(
7742 out.into_iter().map(|v| Value::Number(v as f64)).collect(),
7743 )));
7744 },
7745 #[cfg(not(target_arch = "wasm32"))]
7747 "nn_train" | "训练网" | "ニューラル学習" | "신경망학습" | "ฝึกโครงข่าย" | "آموزش_شبکه" | "درّب_الشبكة" | "אמן_רשת" | "نیٹ_ٹریننگ" | "entraîner_réseau" | "netz_trainieren" | "обучить_сеть" =>
7748 {
7749 let id = self.arg_num(&args, 0, -1.0)? as i64;
7750 let input = self.arg_list_f32(&args, 1);
7751 let target = self.arg_list_f32(&args, 2);
7752 let lr = self.arg_num(&args, 3, 0.01)? as f32;
7753 return Ok(Value::Number(ai::nn_train(id, &input, &target, lr) as f64));
7754 },
7755 #[cfg(not(target_arch = "wasm32"))]
7757 "nn_save" | "保存网" | "網保存" | "신경망저장" | "บันทึกโครงข่าย" | "ذخیره_شبکه" | "احفظ_الشبكة" | "שמור_רשת" | "نیٹ_محفوظ_کرو" | "sauvegarder_réseau" | "netz_speichern" | "сохранить_сеть" =>
7758 {
7759 let id = self.arg_num(&args, 0, -1.0)? as i64;
7760 let path = self.arg_str(&args, 1, "model.lnn");
7761 return Ok(Value::Bool(ai::nn_save(id, &path)));
7762 },
7763 #[cfg(not(target_arch = "wasm32"))]
7765 "nn_load" | "载入网" | "網読込" | "신경망불러오기" | "โหลดโครงข่าย" | "بارگذاری_شبکه" | "حمّل_الشبكة" | "טען_רשת" | "نیٹ_لوڈ" | "charger_réseau" | "netz_laden" | "загрузить_сеть" =>
7766 {
7767 let path = self.arg_str(&args, 0, "model.lnn");
7768 return Ok(Value::Number(ai::nn_load(&path) as f64));
7769 },
7770
7771 #[cfg(not(target_arch = "wasm32"))]
7774 "bt_build" | "建行为树" | "行動木構築" | "행동트리구성" | "สร้างต้นไม้พฤติกรรม" | "ساخت_درخت_رفتار" | "ابنِ_شجرة_السلوك" | "בנה_עץ_התנהגות" | "بی_ٹی_تعمیر" | "construire_arbre_comportement" | "verhaltensbaum_bauen" | "построить_дерево_поведения" =>
7775 {
7776 let spec = self.arg_str(&args, 0, "");
7777 return Ok(Value::Number(ai::bt_build(&spec) as f64));
7778 },
7779 #[cfg(not(target_arch = "wasm32"))]
7781 "bt_set" | "设事实" | "事実設定" | "사실설정" | "ตั้งข้อเท็จจริง" | "تنظیم_واقعیت" | "عيّن_حقيقة" | "קבע_עובדה" | "بی_ٹی_سیٹ" | "définir_arbre_comportement" | "verhaltensbaum_setzen" | "задать_дерево_поведения" =>
7782 {
7783 let id = self.arg_num(&args, 0, -1.0)? as i64;
7784 let key = self.arg_str(&args, 1, "");
7785 let val = self.arg_num(&args, 2, 0.0)? as f32;
7786 ai::bt_set(id, &key, val);
7787 return Ok(Value::Unit);
7788 },
7789 #[cfg(not(target_arch = "wasm32"))]
7791 "bt_tick" | "行为树滴答" | "行動木更新" | "행동트리틱" | "เดินต้นไม้พฤติกรรม" | "تیک_درخت_رفتار" | "نبضة_شجرة_السلوك" | "טיק_עץ_התנהגות" | "بی_ٹی_ٹک" | "tick_arbre_comportement" | "verhaltensbaum_tick" | "тик_дерева_поведения" =>
7792 {
7793 let id = self.arg_num(&args, 0, -1.0)? as i64;
7794 return Ok(Value::Str(ai::bt_tick(id)));
7795 },
7796 #[cfg(not(target_arch = "wasm32"))]
7798 "bt_status" | "行为树状态" | "行動木状態" | "행동트리상태" | "สถานะต้นไม้พฤติกรรม" | "وضعیت_درخت_رفتار" | "حالة_شجرة_السلوك" | "סטטוס_עץ_התנהגות" | "بی_ٹی_حالت" | "statut_arbre_comportement" | "verhaltensbaum_status" | "статус_дерева_поведения" =>
7799 {
7800 let id = self.arg_num(&args, 0, -1.0)? as i64;
7801 return Ok(Value::Number(ai::bt_status(id) as f64));
7802 },
7803
7804 #[cfg(not(target_arch = "wasm32"))]
7807 "dialog_new" | "建对话模型" | "対話モデル作成" | "대화모델생성" | "สร้างโมเดลสนทนา" | "مدل_گفتگوی_جدید" | "نموذج_حوار_جديد" | "מודל_דיאלוג_חדש" | "نیا_مکالمہ_ماڈل" | "nouveau_dialogue" | "neuer_dialog" | "новый_диалог" =>
7808 {
7809 let ctx = self.arg_num(&args, 0, 3.0)?.max(1.0) as usize;
7810 let embed = self.arg_num(&args, 1, 32.0)?.max(1.0) as usize;
7811 let hidden = self.arg_num(&args, 2, 64.0)?.max(1.0) as usize;
7812 let seed = self.arg_num(&args, 3, 1.0)? as u64;
7813 return Ok(Value::Number(
7814 ai::dialog_new(ctx, embed, hidden, seed) as f64
7815 ));
7816 },
7817 #[cfg(not(target_arch = "wasm32"))]
7819 "dialog_learn" | "对话学习" | "対話学習" | "대화학습" | "เรียนรู้สนทนา" | "یادگیری_گفتگو" | "تعلّم_الحوار" | "למד_דיאלוג" | "مکالمہ_سیکھو" | "apprendre_dialogue" | "dialog_lernen" | "обучить_диалог" =>
7820 {
7821 let id = self.arg_num(&args, 0, -1.0)? as i64;
7822 let text = self.arg_str(&args, 1, "");
7823 ai::dialog_learn(id, &text);
7824 return Ok(Value::Unit);
7825 },
7826 #[cfg(not(target_arch = "wasm32"))]
7828 "dialog_load" | "对话载入" | "対話読込" | "대화불러오기" | "โหลดชุดสนทนา" | "بارگذاری_مجموعه_گفتگو" | "حمّل_مجموعة_الحوار" | "טען_מערך_דיאלוג" | "مکالمہ_مجموعہ_لوڈ" | "charger_dialogue" | "dialog_laden" | "загрузить_диалог" =>
7829 {
7830 let id = self.arg_num(&args, 0, -1.0)? as i64;
7831 let path = self.arg_str(&args, 1, "");
7832 return Ok(Value::Number(ai::dialog_load(id, &path) as f64));
7833 },
7834 #[cfg(not(target_arch = "wasm32"))]
7836 "dialog_train" | "对话训练" | "対話訓練" | "대화훈련" | "ฝึกสนทนา" | "آموزش_گفتگو" | "درّب_الحوار" | "אמן_דיאלוג" | "مکالمہ_ٹریننگ" | "entraîner_dialogue" | "dialog_trainieren" | "тренировать_диалог" =>
7837 {
7838 let id = self.arg_num(&args, 0, -1.0)? as i64;
7839 let epochs = self.arg_num(&args, 1, 20.0)?.max(1.0) as usize;
7840 let lr = self.arg_num(&args, 2, 0.1)? as f32;
7841 return Ok(Value::Number(ai::dialog_train(id, epochs, lr) as f64));
7842 },
7843 #[cfg(not(target_arch = "wasm32"))]
7845 "dialog_say" | "对话生成" | "対話生成" | "대화생성" | "พูดสนทนา" | "بگو" | "قل" | "אמור" | "کہو" | "dire_dialogue" | "dialog_sagen" | "сказать_диалог" =>
7846 {
7847 let id = self.arg_num(&args, 0, -1.0)? as i64;
7848 let prompt = self.arg_str(&args, 1, "");
7849 let max = self.arg_num(&args, 2, 24.0)?.max(1.0) as usize;
7850 let temp = self.arg_num(&args, 3, 0.8)? as f32;
7851 return Ok(Value::Str(ai::dialog_say(id, &prompt, max, temp)));
7852 },
7853 #[cfg(not(target_arch = "wasm32"))]
7855 "dialog_save" | "对话存模" | "対話モデル保存" | "대화모델저장" | "บันทึกโมเดลสนทนา" | "ذخیره_مدل_گفتگو" | "احفظ_نموذج_الحوار" | "שמור_מודל_דיאלוג" | "مکالمہ_ماڈل_محفوظ" | "sauvegarder_dialogue" | "dialog_speichern" | "сохранить_диалог" =>
7856 {
7857 let id = self.arg_num(&args, 0, -1.0)? as i64;
7858 let path = self.arg_str(&args, 1, "model.llm");
7859 return Ok(Value::Bool(ai::dialog_save(id, &path)));
7860 },
7861 #[cfg(not(target_arch = "wasm32"))]
7863 "dialog_load_model"
7864 | "对话载模"
7865 | "対話モデル読込"
7866 | "대화모델불러오기"
7867 | "โหลดโมเดลสนทนา" | "بارگذاری_مدل_گفتگو" | "حمّل_نموذج_الحوار" | "טען_מודל_דיאלוג" | "مکالمہ_ماڈل_لوڈ" | "charger_modèle_dialogue" | "dialog_modell_laden" | "загрузить_модель_диалога" => {
7868 let path = self.arg_str(&args, 0, "model.llm");
7869 return Ok(Value::Number(ai::dialog_load_model(&path) as f64));
7870 },
7871
7872 "url_decode" | "网址解码" => {
7875 let s = self.arg_str(&args, 0, "");
7876 let bytes = s.as_bytes();
7877 let mut out = Vec::with_capacity(bytes.len());
7878 let mut i = 0;
7879 while i < bytes.len() {
7880 match bytes[i] {
7881 b'+' => {
7882 out.push(b' ');
7883 i += 1;
7884 },
7885 b'%' if i + 2 < bytes.len() => {
7886 let hex = std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or("");
7887 match u8::from_str_radix(hex, 16) {
7888 Ok(b) => {
7889 out.push(b);
7890 i += 3;
7891 },
7892 Err(_) => {
7893 out.push(bytes[i]);
7894 i += 1;
7895 },
7896 }
7897 },
7898 b => {
7899 out.push(b);
7900 i += 1;
7901 },
7902 }
7903 }
7904 return Ok(Value::Str(String::from_utf8_lossy(&out).into_owned()));
7905 },
7906 "now_unix" | "现在时间" => {
7910 return Ok(Value::Number(now_secs()));
7911 },
7912 "file_exists" | "文件存在" => {
7913 #[cfg(target_arch = "wasm32")]
7914 return Ok(Value::Bool(false));
7915 #[cfg(not(target_arch = "wasm32"))]
7916 {
7917 let path = self.arg_str(&args, 0, "");
7918 return Ok(Value::Bool(std::path::Path::new(&path).exists()));
7919 }
7920 },
7921 "write_file" | "เขียนไฟล์" | "نوشتن_فایل" | "اكتب_الملف" | "כתוב_קובץ" | "فائل_لکھو" => {
7922 #[cfg(target_arch = "wasm32")]
7923 return Ok(Value::Unit);
7924 #[cfg(not(target_arch = "wasm32"))]
7925 {
7926 let path = self.arg_str(&args, 0, "");
7927 let content = self.arg_str(&args, 1, "");
7928 std::fs::write(&path, content.as_bytes())
7929 .map_err(|e| EvalErr::from(format!("write_file '{path}': {e}")))?;
7930 return Ok(Value::Unit);
7931 }
7932 },
7933 "print_file" | "พิมพ์ไฟล์" | "چاپ_فایل" | "اطبع_الملف" | "הדפס_קובץ" | "فائل_چھاپو" => {
7934 let content = self.arg_str(&args, 0, "");
7935 print!("{content}");
7936 return Ok(Value::Unit);
7937 },
7938
7939 "get_args" | "รับอาร์กิวเมนต์" | "دریافت_آرگومانها" | "اجلب_المعاملات" | "קבל_ארגומנטים" | "دلائل_حاصل_کرو" => {
7941 let v: Vec<Value> = std::env::args().map(Value::Str).collect();
7942 return Ok(Value::List(Rc::new(v)));
7943 },
7944
7945 #[cfg(not(target_arch = "wasm32"))]
7950 "list_dir" | "รายการไดเรกทอรี" | "فهرست_پوشه" | "اسرد_المجلد" | "רשום_תיקייה" | "فولڈر_فہرست" => {
7951 let path = self.arg_str(&args, 0, ".");
7952 let mut paths: Vec<String> = Vec::new();
7953 if let Ok(rd) = std::fs::read_dir(&path) {
7954 for e in rd.flatten() {
7955 paths.push(e.path().to_string_lossy().into_owned());
7956 }
7957 }
7958 paths.sort();
7959 let out: Vec<Value> = paths.into_iter().map(Value::Str).collect();
7960 return Ok(Value::List(Rc::new(out)));
7961 },
7962 #[cfg(not(target_arch = "wasm32"))]
7963 "is_dir" | "เป็นไดเรกทอรี" | "آیا_پوشه_است" | "هل_مجلد" | "האם_תיקייה" | "کیا_فولڈر_ہے" => {
7964 let path = self.arg_str(&args, 0, "");
7965 return Ok(Value::Bool(std::path::Path::new(&path).is_dir()));
7966 },
7967 #[cfg(not(target_arch = "wasm32"))]
7968 "is_file" | "เป็นไฟล์" | "آیا_فایل_است" | "هل_ملف" | "האם_קובץ" | "کیا_فائل_ہے" => {
7969 let path = self.arg_str(&args, 0, "");
7970 return Ok(Value::Bool(std::path::Path::new(&path).is_file()));
7971 },
7972 #[cfg(not(target_arch = "wasm32"))]
7973 "path_name" | "ชื่อไฟล์" | "نام_مسیر" | "اسم_المسار" | "שם_נתיב" | "پاتھ_نام" => {
7974 let path = self.arg_str(&args, 0, "");
7975 let name = std::path::Path::new(&path)
7976 .file_name()
7977 .map(|s| s.to_string_lossy().into_owned())
7978 .unwrap_or_default();
7979 return Ok(Value::Str(name));
7980 },
7981 #[cfg(not(target_arch = "wasm32"))]
7982 "path_ext" | "นามสกุลไฟล์" | "پسوند_مسیر" | "امتداد_المسار" | "סיומת_נתיב" | "پاتھ_ایکسٹینشن" => {
7983 let path = self.arg_str(&args, 0, "");
7984 let ext = std::path::Path::new(&path)
7985 .extension()
7986 .map(|s| s.to_string_lossy().to_lowercase())
7987 .unwrap_or_default();
7988 return Ok(Value::Str(ext));
7989 },
7990 #[cfg(not(target_arch = "wasm32"))]
7991 "file_size" | "ขนาดไฟล์" | "اندازه_فایل" | "حجم_الملف" | "גודל_קובץ" | "فائل_سائز" => {
7992 let path = self.arg_str(&args, 0, "");
7993 let sz = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
7994 return Ok(Value::Number(sz as f64));
7995 },
7996 #[cfg(not(target_arch = "wasm32"))]
7997 "file_modified" | "เวลาที่แก้ไข" | "زمان_تغییر_فایل" | "وقت_تعديل_الملف" | "זמן_עדכון_קובץ" | "فائل_ترمیم_وقت" => {
7998 let path = self.arg_str(&args, 0, "");
7999 let secs = std::fs::metadata(&path)
8000 .and_then(|m| m.modified())
8001 .ok()
8002 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8003 .map(|d| d.as_secs_f64())
8004 .unwrap_or(0.0);
8005 return Ok(Value::Number(secs));
8006 },
8007 #[cfg(not(target_arch = "wasm32"))]
8008 "file_created" | "เวลาที่สร้าง" | "زمان_ایجاد_فایل" | "وقت_إنشاء_الملف" | "זמן_יצירת_קובץ" | "فائل_تخلیق_وقت" => {
8009 let path = self.arg_str(&args, 0, "");
8010 let secs = std::fs::metadata(&path)
8011 .ok()
8012 .and_then(|m| m.created().or_else(|_| m.modified()).ok())
8013 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
8014 .map(|d| d.as_secs_f64())
8015 .unwrap_or(0.0);
8016 return Ok(Value::Number(secs));
8017 },
8018 #[cfg(not(target_arch = "wasm32"))]
8019 "make_dir" | "สร้างไดเรกทอรี" | "ساخت_پوشه" | "أنشئ_مجلدا" | "צור_תיקייה" | "فولڈر_بناؤ" => {
8020 let path = self.arg_str(&args, 0, "");
8021 return Ok(Value::Bool(std::fs::create_dir_all(&path).is_ok()));
8022 },
8023 "str_strip_prefix" | "ตัดคำนำหน้า" | "حذف_پیشوند" | "أزل_البادئة" | "הסר_קידומת" | "سابقہ_ہٹاؤ" => {
8025 let s = self.arg_str(&args, 0, "");
8026 let prefix = self.arg_str(&args, 1, "");
8027 return Ok(Value::Str(
8028 s.strip_prefix(&prefix).unwrap_or(&s).to_string(),
8029 ));
8030 },
8031 #[cfg(not(target_arch = "wasm32"))]
8034 "file_magic" | "มายาไฟล์" | "امضای_فایل" | "توقيع_الملف" | "חתימת_קובץ" | "فائل_میجک" => {
8035 let path = self.arg_str(&args, 0, "");
8036 let kind = match std::fs::File::open(&path) {
8037 Ok(mut f) => {
8038 use std::io::Read;
8039 let mut buf = [0u8; 4];
8040 let n = f.read(&mut buf).unwrap_or(0);
8041 if n >= 2 && buf[0] == 0x1f && buf[1] == 0x8b {
8042 "gzip"
8043 } else if n >= 4 && &buf[0..2] == b"PK" {
8044 "zip"
8045 } else {
8046 "other"
8047 }
8048 },
8049 Err(_) => "missing",
8050 };
8051 return Ok(Value::Str(kind.to_string()));
8052 },
8053 #[cfg(not(target_arch = "wasm32"))]
8055 "copy_file" | "คัดลอกไฟล์" | "کپی_فایل" | "انسخ_الملف" | "העתק_קובץ" | "فائل_کاپی" => {
8056 let src = self.arg_str(&args, 0, "");
8057 let dst = self.arg_str(&args, 1, "");
8058 if let Some(parent) = std::path::Path::new(&dst).parent() {
8059 let _ = std::fs::create_dir_all(parent);
8060 }
8061 return Ok(Value::Bool(std::fs::copy(&src, &dst).is_ok()));
8062 },
8063 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8067 "tar_gz_list" | "รายการทาร์" | "فهرست_TAR_GZ" | "اسرد_TAR_GZ" | "רשום_TAR_GZ" | "TAR_GZ_فہرست" => {
8068 let path = self.arg_str(&args, 0, "");
8069 let mut names: Vec<String> = Vec::new();
8070 if let Ok(file) = std::fs::File::open(&path) {
8071 let gz = flate2::read::GzDecoder::new(file);
8072 let mut ar = tar::Archive::new(gz);
8073 if let Ok(entries) = ar.entries() {
8074 for entry in entries.flatten() {
8075 if entry.header().entry_type().is_file() {
8076 if let Ok(p) = entry.path() {
8077 names.push(
8078 p.to_string_lossy()
8079 .trim_start_matches("./")
8080 .replace('\\', "/"),
8081 );
8082 }
8083 }
8084 }
8085 }
8086 }
8087 names.sort();
8088 names.dedup();
8089 let out: Vec<Value> = names.into_iter().map(Value::Str).collect();
8090 return Ok(Value::List(Rc::new(out)));
8091 },
8092 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8096 "tar_gz_read" | "อ่านทาร์" | "خواندن_TAR_GZ" | "اقرأ_TAR_GZ" | "קרא_TAR_GZ" | "TAR_GZ_پڑھو" => {
8097 let path = self.arg_str(&args, 0, "");
8098 let want = self.arg_str(&args, 1, "");
8099 let want = want.trim_start_matches("./").replace('\\', "/");
8100 let mut content = String::new();
8101 if let Ok(file) = std::fs::File::open(&path) {
8102 let gz = flate2::read::GzDecoder::new(file);
8103 let mut ar = tar::Archive::new(gz);
8104 if let Ok(entries) = ar.entries() {
8105 for entry in entries.flatten() {
8106 let mut entry = entry;
8107 let name = match entry.path() {
8108 Ok(p) => p
8109 .to_string_lossy()
8110 .trim_start_matches("./")
8111 .replace('\\', "/"),
8112 Err(_) => continue,
8113 };
8114 if name == want {
8115 use std::io::Read;
8116 let mut buf = Vec::new();
8117 let cap = 256 * 1024;
8118 if entry.take(cap as u64 + 1).read_to_end(&mut buf).is_ok() {
8119 let slice = if buf.len() > cap { &buf[..cap] } else { &buf[..] };
8120 content = String::from_utf8_lossy(slice).into_owned();
8121 }
8122 break;
8123 }
8124 }
8125 }
8126 }
8127 return Ok(Value::Str(content));
8128 },
8129 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8136 "totp_secret" | "โทเทนลับ" | "راز_TOTP" | "سر_TOTP" | "סוד_TOTP" | "TOTP_راز" => {
8137 let mut bytes = [0u8; 20];
8138 rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut bytes);
8139 return Ok(Value::Str(base32_encode(&bytes)));
8140 },
8141 "totp_uri" | "โทเทนยูอาร์ไอ" | "آدرس_TOTP" | "رابط_TOTP" | "כתובת_TOTP" | "TOTP_یو_آر_آئی" => {
8145 let secret = self.arg_str(&args, 0, "");
8146 let account = self.arg_str(&args, 1, "user");
8147 let issuer = self.arg_str(&args, 2, "lingfu");
8148 return Ok(Value::Str(format!(
8149 "otpauth://totp/{issuer}:{account}?secret={secret}&issuer={issuer}&algorithm=SHA1&digits=6&period=30"
8150 )));
8151 },
8152 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8154 "totp_verify" | "โทเทนตรวจ" | "تایید_TOTP" | "تحقق_TOTP" | "אמת_TOTP" | "TOTP_تصدیق" => {
8155 let secret = self.arg_str(&args, 0, "");
8156 let code = self.arg_str(&args, 1, "");
8157 let ok = totp_check(&secret, code.trim());
8158 return Ok(Value::Bool(ok));
8159 },
8160 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
8162 "totp_now" | "โทเทนตอนนี้" | "TOTP_اکنون" | "TOTP_الآن" | "TOTP_עכשיו" | "TOTP_ابھی" => {
8163 let secret = self.arg_str(&args, 0, "");
8164 let step = (crate::runtime::now_secs() as u64) / 30;
8165 return Ok(Value::Str(
8166 totp_code(&secret, step).unwrap_or_default(),
8167 ));
8168 },
8169 #[cfg(not(target_arch = "wasm32"))]
8171 "file_hash" | "แฮชไฟล์" | "درهم_فایل" | "بصمة_الملف" | "גיבוב_קובץ" | "فائل_ہیش" => {
8172 let path = self.arg_str(&args, 0, "");
8173 match std::fs::read(&path) {
8174 Ok(bytes) => {
8175 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(&bytes))))
8176 },
8177 Err(_) => return Ok(Value::Str(String::new())),
8178 }
8179 },
8180 "hash_hex" | "แฮชสตริง" | "درهم_هگزادسیمال" | "بصمة_سداسية" | "גיבוב_הקסדצימלי" | "ہیکس_ہیش" => {
8182 let s = self.arg_str(&args, 0, "");
8183 return Ok(Value::Str(hex_encode(&ling_crypto::Blake3::hash(
8184 s.as_bytes(),
8185 ))));
8186 },
8187 #[cfg(not(target_arch = "wasm32"))]
8189 "env_get" | "รับตัวแปรแวดล้อม" | "دریافت_متغیر_محیطی" | "اجلب_متغير_البيئة" | "קבל_משתנה_סביבה" | "ماحولیاتی_متغیر_حاصل_کرو" => {
8190 let name = self.arg_str(&args, 0, "");
8191 let dflt = self.arg_str(&args, 1, "");
8192 return Ok(Value::Str(std::env::var(&name).unwrap_or(dflt)));
8193 },
8194
8195 "to_number" | "转数字" => {
8199 let s = self.arg_str(&args, 0, "");
8200 return Ok(Value::Number(s.trim().parse().unwrap_or(0.0)));
8201 },
8202 "sha256_hex" | "SHA256哈希" => {
8207 use sha2::Digest;
8208 let s = self.arg_str(&args, 0, "");
8209 let mut h = sha2::Sha256::new();
8210 h.update(s.as_bytes());
8211 return Ok(Value::Str(hex_encode(&h.finalize())));
8212 },
8213 "hex_to_number" | "十六进制转数字" => {
8216 let s = self.arg_str(&args, 0, "");
8217 let v = u64::from_str_radix(s.trim(), 16).unwrap_or(0);
8218 return Ok(Value::Number(v as f64));
8219 },
8220 "split" | "str_split" | "แยก" | "جداسازی" | "قسّم" | "פצל" | "تقسیم_کرو" => {
8221 let s = self.arg_str(&args, 0, "");
8222 let sep = self.arg_str(&args, 1, "\n");
8223 let sep = if sep.is_empty() { "\n".into() } else { sep };
8224 let parts: Vec<Value> = s
8225 .split(sep.as_str())
8226 .map(|p| Value::Str(p.to_string()))
8227 .collect();
8228 return Ok(Value::List(Rc::new(parts)));
8229 },
8230 "trim" | "str_trim" | "ตัดช่องว่าง" | "حذف_فاصله" | "اقتطع_الفراغات" | "חתוך_רווחים" | "خالی_جگہ_کاٹو" => {
8231 let s = self.arg_str(&args, 0, "");
8232 return Ok(Value::Str(s.trim().to_string()));
8233 },
8234 "starts_with" | "str_starts_with" | "เริ่มด้วย" | "شروع_میشود_با" | "يبدأ_بـ" | "מתחיל_ב" | "شروع_ہوتا_ہے" => {
8235 let s = self.arg_str(&args, 0, "");
8236 let prefix = self.arg_str(&args, 1, "");
8237 return Ok(Value::Bool(s.starts_with(prefix.as_str())));
8238 },
8239 "ends_with" | "str_ends_with" | "ลงท้ายด้วย" | "پایان_مییابد_با" | "ينتهي_بـ" | "מסתיים_ב" | "ختم_ہوتا_ہے" => {
8240 let s = self.arg_str(&args, 0, "");
8241 let suffix = self.arg_str(&args, 1, "");
8242 return Ok(Value::Bool(s.ends_with(suffix.as_str())));
8243 },
8244 "str_replace" | "แทนสตริง" | "جایگزینی_رشته" | "استبدل_النص" | "החלף_מחרוזת" | "اسٹرنگ_تبدیل" => {
8245 let s = self.arg_str(&args, 0, "");
8246 let from = self.arg_str(&args, 1, "");
8247 let to = self.arg_str(&args, 2, "");
8248 return Ok(Value::Str(s.replace(from.as_str(), to.as_str())));
8249 },
8250 "str_find" | "หาในสตริง" | "جستجوی_رشته" | "ابحث_في_النص" | "חפש_מחרוזת" | "اسٹرنگ_تلاش" => {
8251 let s = self.arg_str(&args, 0, "");
8252 let needle = self.arg_str(&args, 1, "");
8253 let pos = s
8255 .find(needle.as_str())
8256 .map(|byte_i| s[..byte_i].chars().count() as f64)
8257 .unwrap_or(-1.0);
8258 return Ok(Value::Number(pos));
8259 },
8260 "substr" | "str_slice" | "ส่วนสตริง" | "زیررشته" | "جزء_النص" | "תת_מחרוזת" | "ذیلی_اسٹرنگ" => {
8261 let s = self.arg_str(&args, 0, "");
8262 let start = self.arg_num(&args, 1, 0.0)? as usize;
8263 let len = args
8264 .get(2)
8265 .map(|v| self.to_number(v).unwrap_or(999999.0) as usize)
8266 .unwrap_or_else(|| s.chars().count().saturating_sub(start));
8267 let chars: Vec<char> = s.chars().collect();
8268 let end = (start + len).min(chars.len());
8269 let slice: String = chars.get(start..end).unwrap_or(&[]).iter().collect();
8270 return Ok(Value::Str(slice));
8271 },
8272 "to_str" | "str" | "num_str" | "แปลงสตริง" | "تبدیل_به_رشته" | "حوّل_لنص" | "המר_למחרוזת" | "اسٹرنگ_میں_بدلو" => {
8273 let v = args.into_iter().next().unwrap_or(Value::Unit);
8274 return Ok(Value::Str(v.to_string()));
8275 },
8276 "str_repeat" | "ทำซ้ำสตริง" | "تکرار_رشته" | "كرّر_النص" | "חזור_על_מחרוזת" | "اسٹرنگ_دہراؤ" => {
8277 let s = self.arg_str(&args, 0, "");
8278 let n = self.arg_num(&args, 1, 1.0)? as usize;
8279 return Ok(Value::Str(s.repeat(n)));
8280 },
8281 "str_upper" => {
8282 let s = self.arg_str(&args, 0, "");
8283 return Ok(Value::Str(s.to_uppercase()));
8284 },
8285 "str_lower" => {
8286 let s = self.arg_str(&args, 0, "");
8287 return Ok(Value::Str(s.to_lowercase()));
8288 },
8289 "str_len" | "len" | "ความยาว" | "长度" | "長さ" | "길이" | "طول_رشته" | "طول_النص" | "אורך_מחרוזת" | "اسٹرنگ_لمبائی" => {
8290 match args.first() {
8291 Some(Value::Str(s)) => return Ok(Value::Number(s.chars().count() as f64)),
8292 Some(Value::List(v)) => return Ok(Value::Number(v.len() as f64)),
8293 _ => return Ok(Value::Number(0.0)),
8294 }
8295 },
8296
8297 "hash_str" | "แฮช" | "درهم_رشته" | "بصمة_نص" | "גיבוב_מחרוזת" | "اسٹرنگ_ہیش" => {
8299 let s = self.arg_str(&args, 0, "");
8300 let mut h: u64 = 14695981039346656037_u64;
8301 for b in s.bytes() {
8302 h ^= b as u64;
8303 h = h.wrapping_mul(1099511628211);
8304 }
8305 return Ok(Value::Number((h & 0xFFFFFF) as f64 / 16777215.0));
8306 },
8307 "hash_int" | "แฮชจำนวน" | "درهم_عدد" | "بصمة_عدد" | "גיבוב_מספר" | "نمبر_ہیش" => {
8308 let s = self.arg_str(&args, 0, "");
8309 let n = self.arg_num(&args, 1, 100.0)? as u64;
8310 let mut h: u64 = 14695981039346656037_u64;
8311 for b in s.bytes() {
8312 h ^= b as u64;
8313 h = h.wrapping_mul(1099511628211);
8314 }
8315 return Ok(Value::Number((h % n.max(1)) as f64));
8316 },
8317
8318 "list_new" | "รายการใหม่" | "新建列表" | "新規リスト" | "새목록" | "فهرست_جدید" | "قائمة_جديدة" | "רשימה_חדשה" | "نئی_فہرست" | "nouvelle_liste" | "neue_liste" | "новый_список" =>
8320 {
8321 return Ok(Value::List(Rc::new(Vec::new())));
8322 },
8323 "list_push" | "เพิ่มรายการ" | "列表添加" | "リスト追加" | "목록추가" | "افزودن_به_فهرست" | "أضف_للقائمة" | "הוסף_לרשימה" | "فہرست_میں_شامل_کرو" | "ajouter_liste" | "liste_anhängen" | "добавить_в_список" =>
8324 {
8325 let lst = args
8326 .first()
8327 .cloned()
8328 .unwrap_or(Value::List(Rc::new(vec![])));
8329 let val = args.get(1).cloned().unwrap_or(Value::Unit);
8330 if let Value::List(mut v) = lst {
8331 Rc::make_mut(&mut v).push(val);
8332 return Ok(Value::List(v));
8333 }
8334 return Ok(Value::List(Rc::new(vec![val])));
8335 },
8336 "list_get" | "รับรายการ" | "取元素" | "要素取得" | "요소가져오기" | "دریافت_از_فهرست" | "اجلب_من_القائمة" | "קבל_מרשימה" | "فہرست_سے_حاصل_کرو" | "obtenir_liste" | "liste_abrufen" | "получить_из_списка" =>
8337 {
8338 let i = self.arg_num(&args, 1, 0.0)? as usize;
8340 if let Some(Value::List(v)) = args.first() {
8341 return Ok(v.get(i).cloned().unwrap_or(Value::Str(String::new())));
8342 }
8343 return Ok(Value::Str(String::new()));
8344 },
8345 "list_max" | "列表最大值" => {
8349 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8350 let default = self.arg_num(&args, 1, 0.0)?;
8351 if let Value::List(v) = lst {
8352 let mut best = default;
8353 let mut any = false;
8354 for item in v.iter() {
8355 if let Value::Number(n) = item {
8356 if !any || *n > best {
8357 best = *n;
8358 any = true;
8359 }
8360 }
8361 }
8362 return Ok(Value::Number(best));
8363 }
8364 return Ok(Value::Number(default));
8365 },
8366 "list_min" | "列表最小值" => {
8367 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8368 let default = self.arg_num(&args, 1, 0.0)?;
8369 if let Value::List(v) = lst {
8370 let mut best = default;
8371 let mut any = false;
8372 for item in v.iter() {
8373 if let Value::Number(n) = item {
8374 if !any || *n < best {
8375 best = *n;
8376 any = true;
8377 }
8378 }
8379 }
8380 return Ok(Value::Number(best));
8381 }
8382 return Ok(Value::Number(default));
8383 },
8384 "list_set" | "ตั้งรายการ" | "设元素" | "要素設定" | "요소설정" | "تنظیم_عنصر_فهرست" | "عيّن_عنصر_القائمة" | "קבע_איבר_רשימה" | "فہرست_سیٹ" =>
8388 {
8389 let idx = self.arg_num(&args, 1, 0.0)? as usize;
8390 let mut ai = args.into_iter();
8391 let lst = ai.next().unwrap_or(Value::List(Rc::new(vec![])));
8392 ai.next(); let val = ai.next().unwrap_or(Value::Unit);
8394 if let Value::List(mut v) = lst {
8395 if idx < v.len() {
8396 Rc::make_mut(&mut v)[idx] = val;
8397 }
8398 return Ok(Value::List(v));
8399 }
8400 return Ok(Value::List(Rc::new(vec![])));
8401 },
8402 "list_join" | "join" | "รวมรายการ" | "连接" | "連結" | "연결" | "پیوستن_فهرست" | "اربط_القائمة" | "חבר_רשימה" | "فہرست_جوڑو" =>
8403 {
8404 let lst = args
8405 .first()
8406 .cloned()
8407 .unwrap_or(Value::List(Rc::new(vec![])));
8408 let sep = args.get(1).map(|v| v.to_string()).unwrap_or_default();
8409 if let Value::List(v) = lst {
8410 return Ok(Value::Str(
8411 v.iter()
8412 .map(|x| x.to_string())
8413 .collect::<Vec<_>>()
8414 .join(&sep),
8415 ));
8416 }
8417 return Ok(Value::Str(String::new()));
8418 },
8419 "list_map" | "映射列表" => {
8428 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8429 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8430 if let Value::List(v) = lst {
8431 let mut out = Vec::with_capacity(v.len());
8432 for item in v.iter() {
8433 out.push(self.call_value(f.clone(), vec![item.clone()])?);
8434 }
8435 return Ok(Value::List(Rc::new(out)));
8436 }
8437 return Ok(Value::List(Rc::new(vec![])));
8438 },
8439 "list_filter" | "过滤列表" => {
8440 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8441 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8442 if let Value::List(v) = lst {
8443 let mut out = Vec::new();
8444 for item in v.iter() {
8445 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8446 out.push(item.clone());
8447 }
8448 }
8449 return Ok(Value::List(Rc::new(out)));
8450 }
8451 return Ok(Value::List(Rc::new(vec![])));
8452 },
8453 "list_find" | "查找列表" => {
8455 let lst = args.first().cloned().unwrap_or(Value::List(Rc::new(vec![])));
8456 let f = args.get(1).cloned().unwrap_or(Value::Unit);
8457 if let Value::List(v) = lst {
8458 for item in v.iter() {
8459 if matches!(self.call_value(f.clone(), vec![item.clone()])?, Value::Bool(true)) {
8460 return Ok(item.clone());
8461 }
8462 }
8463 }
8464 return Ok(Value::Unit);
8465 },
8466 #[cfg(not(target_arch = "wasm32"))]
8470 "blob_f32" | "blob_i32" => {
8471 let s = self.arg_str(&args, 0, "");
8472 let is_i32 = name == "blob_i32";
8473 match decode_blob(&s) {
8474 Ok(bytes) => {
8475 let mut out = Vec::with_capacity(bytes.len() / 4);
8476 for ch in bytes.chunks_exact(4) {
8477 let arr = [ch[0], ch[1], ch[2], ch[3]];
8478 let n = if is_i32 {
8479 i32::from_le_bytes(arr) as f64
8480 } else {
8481 f32::from_le_bytes(arr) as f64
8482 };
8483 out.push(Value::Number(n));
8484 }
8485 return Ok(Value::List(Rc::new(out)));
8486 },
8487 Err(e) => {
8488 eprintln!("blob decode failed: {e}");
8489 return Ok(Value::List(Rc::new(vec![])));
8490 },
8491 }
8492 },
8493
8494 "svg_begin" | "开始SVG" | "เริ่มSVG" | "شروع_SVG" | "ابدأ_SVG" | "התחל_SVG" | "SVG_شروع" | "commencer_svg" | "svg_beginnen" | "начать_svg" => {
8501 let path = self.arg_str(&args, 0, "output.svg");
8502 let width = self.arg_num(&args, 1, 800.0)?;
8503 let height = self.arg_num(&args, 2, 600.0)?;
8504 *self.svg.borrow_mut() = Some(SvgWriter::new(path, width, height));
8505 return Ok(Value::Unit);
8506 },
8507
8508 "svg_rect" | "SVG矩形" | "SVGสี่เหลี่ยม" | "مستطیل_SVG" | "مستطيل_SVG" | "מלבן_SVG" | "SVG_مستطیل" | "rectangle_svg" | "svg_rechteck" | "прямоугольник_svg" => {
8509 let x = self.arg_num(&args, 0, 0.0)?;
8510 let y = self.arg_num(&args, 1, 0.0)?;
8511 let w = self.arg_num(&args, 2, 10.0)?;
8512 let h = self.arg_num(&args, 3, 10.0)?;
8513 let fill = self.arg_str(&args, 4, "#ffffff");
8514 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8515 svg.elements.push(format!(
8516 "<rect x=\"{x:.1}\" y=\"{y:.1}\" width=\"{w:.1}\" \
8517 height=\"{h:.1}\" fill=\"{fill}\"/>"
8518 ));
8519 }
8520 return Ok(Value::Unit);
8521 },
8522
8523 "svg_circle" | "SVG圆形" | "SVGวงกลม" | "دایره_SVG" | "دائرة_SVG" | "עיגול_SVG" | "SVG_دائرہ" | "cercle_svg" | "svg_kreis" | "круг_svg" => {
8524 let cx = self.arg_num(&args, 0, 0.0)?;
8525 let cy = self.arg_num(&args, 1, 0.0)?;
8526 let r = self.arg_num(&args, 2, 5.0)?;
8527 let fill = self.arg_str(&args, 3, "#ffffff");
8528 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8529 svg.elements.push(format!(
8530 "<circle cx=\"{cx:.1}\" cy=\"{cy:.1}\" r=\"{r:.1}\" fill=\"{fill}\"/>"
8531 ));
8532 }
8533 return Ok(Value::Unit);
8534 },
8535
8536 "svg_line" | "SVG线段" | "SVGเส้น" | "خط_SVG" | "خط_SVG" | "קו_SVG" | "SVG_لکیر" | "ligne_svg" | "svg_linie" | "линия_svg" => {
8537 let x1 = self.arg_num(&args, 0, 0.0)?;
8538 let y1 = self.arg_num(&args, 1, 0.0)?;
8539 let x2 = self.arg_num(&args, 2, 0.0)?;
8540 let y2 = self.arg_num(&args, 3, 0.0)?;
8541 let stroke = self.arg_str(&args, 4, "#ffffff");
8542 let sw = self.arg_num(&args, 5, 1.0)?;
8543 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8544 svg.elements.push(format!(
8545 "<line x1=\"{x1:.1}\" y1=\"{y1:.1}\" x2=\"{x2:.1}\" y2=\"{y2:.1}\" \
8546 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8547 ));
8548 }
8549 return Ok(Value::Unit);
8550 },
8551
8552 "svg_polyline" | "SVG折线" | "SVGเส้นหัก" | "چندخطی_SVG" | "خط_متعدد_SVG" | "קו_שבור_SVG" | "SVG_پولی_لائن" | "polyligne_svg" | "svg_polylinie" | "ломаная_svg" => {
8553 let pts = self.arg_str(&args, 0, "");
8554 let stroke = self.arg_str(&args, 1, "#ffffff");
8555 let sw = self.arg_num(&args, 2, 1.0)?;
8556 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8557 svg.elements.push(format!(
8558 "<polyline points=\"{pts}\" fill=\"none\" \
8559 stroke=\"{stroke}\" stroke-width=\"{sw:.1}\"/>"
8560 ));
8561 }
8562 return Ok(Value::Unit);
8563 },
8564
8565 "svg_text" | "SVG文本" | "SVGข้อความ" | "متن_SVG" | "نص_SVG" | "טקסט_SVG" | "SVG_متن" | "texte_svg" | "текст_svg" => {
8566 let x = self.arg_num(&args, 0, 0.0)?;
8567 let y = self.arg_num(&args, 1, 0.0)?;
8568 let text = self.arg_str(&args, 2, "");
8569 let fill = self.arg_str(&args, 3, "#ffffff");
8570 let size = self.arg_num(&args, 4, 12.0)?;
8571 if let Some(svg) = self.svg.borrow_mut().as_mut() {
8572 let safe = text
8573 .replace('&', "&")
8574 .replace('<', "<")
8575 .replace('>', ">");
8576 svg.elements.push(format!(
8577 "<text x=\"{x:.1}\" y=\"{y:.1}\" fill=\"{fill}\" \
8578 font-family=\"monospace\" font-size=\"{size:.0}\">{safe}</text>"
8579 ));
8580 }
8581 return Ok(Value::Unit);
8582 },
8583
8584 "svg_end" | "结束SVG" | "จบSVG" | "پایان_SVG" | "أنهِ_SVG" | "סיים_SVG" | "SVG_ختم" | "terminer_svg" | "svg_beenden" | "закончить_svg" => {
8585 {
8586 let borrow = self.svg.borrow();
8587 if let Some(svg) = borrow.as_ref() {
8588 svg.save()
8589 .map_err(|e| EvalErr::from(format!("svg_end: {e}")))?;
8590 }
8591 }
8592 *self.svg.borrow_mut() = None;
8593 return Ok(Value::Unit);
8594 },
8595
8596 "hsl_color" | "HSL颜色" | "สีHSL" | "رنگ_HSL" | "لون_HSL" | "צבע_HSL" | "HSL_رنگ" | "couleur_hsl" | "hsl_farbe" | "цвет_hsl" => {
8597 let h = self.arg_num(&args, 0, 0.0)?;
8598 let s = self.arg_num(&args, 1, 70.0)?;
8599 let l = self.arg_num(&args, 2, 50.0)?;
8600 return Ok(Value::Str(hsl_to_hex(h, s, l)));
8601 },
8602
8603 #[cfg(not(target_arch = "wasm32"))]
8609 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" | "ورودی_FFT" | "أدخل_FFT" | "הזנת_FFT" | "FFT_ان_پٹ" | "fft_entrée" | "fft_eingabe" | "fft_вход" =>
8610 {
8611 if let Some(Value::List(v)) = args.first() {
8612 let samples: Vec<f32> = v
8613 .iter()
8614 .filter_map(|x| {
8615 if let Value::Number(n) = x {
8616 Some(*n as f32)
8617 } else {
8618 None
8619 }
8620 })
8621 .collect();
8622 self.fft.borrow_mut().push_samples(&samples);
8623 }
8624 return Ok(Value::Unit);
8625 },
8626
8627 #[cfg(not(target_arch = "wasm32"))]
8629 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" | "باندهای_FFT" | "نطاقات_FFT" | "פסי_FFT" | "FFT_بینڈز" | "fft_bandes" | "fft_bänder" | "fft_полосы" =>
8630 {
8631 let n = self.arg_num(&args, 0, 32.0)? as usize;
8632 let bands = self.fft.borrow().freq_bands(n);
8633 *self.fft_bands_cache.borrow_mut() = bands.clone();
8634 return Ok(Value::List(Rc::new(
8635 bands.into_iter().map(|v| Value::Number(v as f64)).collect(),
8636 )));
8637 },
8638
8639 #[cfg(not(target_arch = "wasm32"))]
8641 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" | "ضرب_FFT" | "نبضة_FFT" | "פעימת_FFT" | "FFT_دھڑکن" | "fft_battement" | "fft_takt" | "fft_удар" =>
8642 {
8643 return Ok(Value::Bool(self.fft.borrow().is_beat()));
8644 },
8645
8646 #[cfg(not(target_arch = "wasm32"))]
8648 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" | "نسبت_ضرب_FFT" | "نسبة_نبضة_FFT" | "יחס_פעימת_FFT" | "FFT_بیٹ_تناسب" =>
8649 {
8650 return Ok(Value::Number(self.fft.borrow().beat_ratio() as f64));
8651 },
8652
8653 #[cfg(not(target_arch = "wasm32"))]
8655 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" | "RMS_صدا" | "جذر_متوسط_مربع_FFT" | "RMS_של_FFT" | "FFT_RMS" => {
8656 return Ok(Value::Number(self.fft.borrow().rms() as f64));
8657 },
8658
8659 #[cfg(not(target_arch = "wasm32"))]
8661 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" | "فرکانس_غالب" | "التردد_السائد" | "תדר_דומיננטי" | "غالب_فریکوئنسی" | "fft_fréquence_dominante" | "fft_dominante_frequenz" | "fft_доминирующая_частота" =>
8662 {
8663 return Ok(Value::Number(self.fft.borrow().dominant_freq() as f64));
8664 },
8665
8666 #[cfg(target_arch = "wasm32")]
8668 "fft_push" | "วิเคราะห์เสียง" | "频谱输入" | "FFT入力" | "FFT입력" | "ورودی_FFT" | "أدخل_FFT" | "הזנת_FFT" | "FFT_ان_پٹ" | "fft_entrée" | "fft_eingabe" | "fft_вход" =>
8669 {
8670 return Ok(Value::Unit);
8671 },
8672 #[cfg(target_arch = "wasm32")]
8673 "fft_bands" | "แถบความถี่" | "频段" | "周波数帯" | "주파수대" | "باندهای_FFT" | "نطاقات_FFT" | "פסי_FFT" | "FFT_بینڈز" | "fft_bandes" | "fft_bänder" | "fft_полосы" =>
8674 {
8675 let n = self.arg_num(&args, 0, 32.0)? as usize;
8676 return Ok(Value::List(vec![Value::Number(0.0); n].into()));
8677 },
8678 #[cfg(target_arch = "wasm32")]
8679 "fft_beat" | "จังหวะเสียง" | "节拍检测" | "ビート検出" | "비트" | "ضرب_FFT" | "نبضة_FFT" | "פעימת_FFT" | "FFT_دھڑکن" | "fft_battement" | "fft_takt" | "fft_удар" =>
8680 {
8681 return Ok(Value::Bool(false));
8682 },
8683 #[cfg(target_arch = "wasm32")]
8684 "fft_beat_ratio" | "อัตราจังหวะ" | "节拍比" | "ビート比" | "비트비율" | "نسبت_ضرب_FFT" | "نسبة_نبضة_FFT" | "יחס_פעימת_FFT" | "FFT_بیٹ_تناسب" =>
8685 {
8686 return Ok(Value::Number(1.0));
8687 },
8688 #[cfg(target_arch = "wasm32")]
8689 "fft_rms" | "ระดับRMS" | "均方根" | "二乗平均" | "RMS레벨" | "RMS_صدا" | "جذر_متوسط_مربع_FFT" | "RMS_של_FFT" | "FFT_RMS" => {
8690 return Ok(Value::Number(0.0));
8691 },
8692 #[cfg(target_arch = "wasm32")]
8693 "fft_dominant_freq" | "ความถี่หลัก" | "主频" | "主要周波数" | "주파수" | "فرکانس_غالب" | "التردد_السائد" | "תדר_דומיננטי" | "غالب_فریکوئنسی" | "fft_fréquence_dominante" | "fft_dominante_frequenz" | "fft_доминирующая_частота" =>
8694 {
8695 return Ok(Value::Number(0.0));
8696 },
8697
8698 "tex_checkerboard" | "ลายตารางหมากรุก" | "بافت_شطرنجی" | "نسيج_رقعة_الشطرنج" | "מרקם_שחמט" | "شطرنج_ٹیکسچر" => {
8706 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8707 let tiles = self.arg_num(&args, 4, 8.0)? as u32;
8708 let (r1, g1, b1) = (
8709 self.arg_num(&args, 5, 255.)? as u32,
8710 self.arg_num(&args, 6, 255.)? as u32,
8711 self.arg_num(&args, 7, 255.)? as u32,
8712 );
8713 let (r2, g2, b2) = (
8714 self.arg_num(&args, 8, 0.)? as u32,
8715 self.arg_num(&args, 9, 0.)? as u32,
8716 self.arg_num(&args, 10, 0.)? as u32,
8717 );
8718 let c1 = (r1 << 16) | (g1 << 8) | b1;
8719 let c2 = (r2 << 16) | (g2 << 8) | b2;
8720 let mut gfx = self.gfx.borrow_mut();
8721 let (bw, bh) = (gfx.width, gfx.height);
8722 for row in 0..th {
8723 for col in 0..tw {
8724 let cx = col as u32 * tiles / tw as u32;
8725 let cy = row as u32 * tiles / th as u32;
8726 let (dx, dy) = (tx + col, ty + row);
8727 if dx < bw && dy < bh {
8728 gfx.buffer[dy * bw + dx] = if (cx + cy) % 2 == 0 { c1 } else { c2 };
8729 }
8730 }
8731 }
8732 return Ok(Value::Unit);
8733 },
8734
8735 "tex_gradient" | "ลายไล่สี" | "بافت_گرادیان" | "نسيج_متدرج" | "מרקם_גרדיאנט" | "گریڈینٹ_ٹیکسچر" => {
8737 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8738 let angle = self.arg_num(&args, 4, 0.0)? as f32;
8739 let (r1, g1, b1) = (
8740 self.arg_num(&args, 5, 0.)? as f32 / 255.,
8741 self.arg_num(&args, 6, 0.)? as f32 / 255.,
8742 self.arg_num(&args, 7, 0.)? as f32 / 255.,
8743 );
8744 let (r2, g2, b2) = (
8745 self.arg_num(&args, 8, 255.)? as f32 / 255.,
8746 self.arg_num(&args, 9, 255.)? as f32 / 255.,
8747 self.arg_num(&args, 10, 255.)? as f32 / 255.,
8748 );
8749 let (ca, sa) = (angle.to_radians().cos(), angle.to_radians().sin());
8750 let mut gfx = self.gfx.borrow_mut();
8751 let (bw, bh) = (gfx.width, gfx.height);
8752 for row in 0..th {
8753 for col in 0..tw {
8754 let nx = col as f32 / tw as f32 - 0.5;
8755 let ny = row as f32 / th as f32 - 0.5;
8756 let t = ((nx * ca + ny * sa + 0.707) / 1.414).clamp(0., 1.);
8757 let (dx, dy) = (tx + col, ty + row);
8758 if dx < bw && dy < bh {
8759 gfx.buffer[dy * bw + dx] =
8760 tex_rgb(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
8761 }
8762 }
8763 }
8764 return Ok(Value::Unit);
8765 },
8766
8767 "tex_noise" | "ลายนอยส์" | "بافت_نویز" | "نسيج_ضجيج" | "מרקם_רעש" | "نوائز_ٹیکسچر" => {
8769 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8770 let scale = self.arg_num(&args, 4, 4.0)? as f32;
8771 let octaves = self.arg_num(&args, 5, 4.0)? as u32;
8772 let seed = self.arg_num(&args, 6, 0.0)? as u32;
8773 let palette = self.arg_str(&args, 7, "rainbow");
8774 let mut gfx = self.gfx.borrow_mut();
8775 let (bw, bh) = (gfx.width, gfx.height);
8776 for row in 0..th {
8777 for col in 0..tw {
8778 let v = tex_fbm(
8779 col as f32 * scale / tw as f32,
8780 row as f32 * scale / th as f32,
8781 octaves,
8782 seed,
8783 );
8784 let [r, g, b] = tex_palette(&palette, v);
8785 let (dx, dy) = (tx + col, ty + row);
8786 if dx < bw && dy < bh {
8787 gfx.buffer[dy * bw + dx] = tex_rgb(r, g, b);
8788 }
8789 }
8790 }
8791 return Ok(Value::Unit);
8792 },
8793
8794 "tex_freq_map" | "ลายความถี่" | "نقشه_فرکانس_بافت" | "خريطة_تردد_النسيج" | "מפת_תדר_מרקם" | "فریکوئنسی_میپ_ٹیکسچر" => {
8797 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8798 let time = self.arg_num(&args, 4, 0.0)? as f32;
8799 let speed = self.arg_num(&args, 5, 0.3)? as f32;
8800 let palette = self.arg_str(&args, 6, "rainbow");
8801 let bands: Vec<f32> = {
8802 let c = self.fft_bands_cache.borrow();
8803 if c.is_empty() {
8804 vec![0.0; 32]
8805 } else {
8806 c.clone()
8807 }
8808 };
8809 let n = bands.len().max(1);
8810 let mut gfx = self.gfx.borrow_mut();
8811 let (bw, bh) = (gfx.width, gfx.height);
8812 for row in 0..th {
8813 for col in 0..tw {
8814 let band_idx = (col * n / tw.max(1)).min(n - 1);
8815 let mag = bands[band_idx].clamp(0., 1.);
8816 let fill_y = (mag * th as f32) as usize;
8817 if row >= th.saturating_sub(fill_y) {
8818 let t = (col as f32 / tw as f32 + time * speed) % 1.0;
8819 let [r, g, b] = tex_palette(&palette, t);
8820 let bright = mag * (1.0 - row as f32 / th as f32 * 0.5);
8821 let (dx, dy) = (tx + col, ty + row);
8822 if dx < bw && dy < bh {
8823 gfx.buffer[dy * bw + dx] =
8824 tex_rgb(r * bright, g * bright, b * bright);
8825 }
8826 }
8827 }
8828 }
8829 return Ok(Value::Unit);
8830 },
8831
8832 "tex_spiral" | "ลายเกลียวหมุน" | "بافت_مارپیچ" | "نسيج_حلزوني" | "מרקם_ספירלה" | "سرپیچ_ٹیکسچر" => {
8834 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8835 let freq = self.arg_num(&args, 4, 5.0)? as f32;
8836 let n_bands = self.arg_num(&args, 5, 8.0)? as f32;
8837 let time = self.arg_num(&args, 6, 0.0)? as f32;
8838 let palette = self.arg_str(&args, 7, "rainbow");
8839 let mut gfx = self.gfx.borrow_mut();
8840 let (bw, bh) = (gfx.width, gfx.height);
8841 for row in 0..th {
8842 for col in 0..tw {
8843 let nx = col as f32 / tw as f32 - 0.5;
8844 let ny = row as f32 / th as f32 - 0.5;
8845 let r = (nx * nx + ny * ny).sqrt();
8846 let theta = ny.atan2(nx);
8847 let t = ((r * freq - theta / std::f32::consts::TAU + time * 0.5) * n_bands
8848 % 1.0)
8849 .abs();
8850 let [cr, cg, cb] = tex_palette(&palette, t);
8851 let (dx, dy) = (tx + col, ty + row);
8852 if dx < bw && dy < bh {
8853 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8854 }
8855 }
8856 }
8857 return Ok(Value::Unit);
8858 },
8859
8860 "tex_ripple" | "ลายระลอก" | "بافت_موج" | "نسيج_تموج" | "מרקם_אדווה" | "ریپل_ٹیکسچر" => {
8862 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8863 let freq = self.arg_num(&args, 4, 10.0)? as f32;
8864 let rcx = self.arg_num(&args, 5, 0.5)? as f32;
8865 let rcy = self.arg_num(&args, 6, 0.5)? as f32;
8866 let time = self.arg_num(&args, 7, 0.0)? as f32;
8867 let palette = self.arg_str(&args, 8, "ocean");
8868 let mut gfx = self.gfx.borrow_mut();
8869 let (bw, bh) = (gfx.width, gfx.height);
8870 for row in 0..th {
8871 for col in 0..tw {
8872 let nx = col as f32 / tw as f32 - rcx;
8873 let ny = row as f32 / th as f32 - rcy;
8874 let r = (nx * nx + ny * ny).sqrt();
8875 let t = ((r * freq - time) % 1.0).abs();
8876 let [cr, cg, cb] = tex_palette(&palette, t);
8877 let (dx, dy) = (tx + col, ty + row);
8878 if dx < bw && dy < bh {
8879 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8880 }
8881 }
8882 }
8883 return Ok(Value::Unit);
8884 },
8885
8886 "tex_mandelbrot" | "ลายแมนเดลบรอต" | "بافت_ماندلبرو" | "نسيج_مانديلبروت" | "מרקם_מנדלברוט" | "مینڈل_بروٹ_ٹیکسچر" => {
8888 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8889 let zoom = self.arg_num(&args, 4, 1.0)?;
8890 let mcx = self.arg_num(&args, 5, -0.5)?;
8891 let mcy = self.arg_num(&args, 6, 0.0)?;
8892 let max_iter = self.arg_num(&args, 7, 64.0)? as u32;
8893 let palette = self.arg_str(&args, 8, "psychedelic");
8894 let mut gfx = self.gfx.borrow_mut();
8895 let (bw, bh) = (gfx.width, gfx.height);
8896 for row in 0..th {
8897 for col in 0..tw {
8898 let zx0 = (col as f64 / tw as f64 - 0.5) / zoom + mcx;
8899 let zy0 = (row as f64 / th as f64 - 0.5) / zoom + mcy;
8900 let mut x = 0.0f64;
8901 let mut y = 0.0f64;
8902 let mut i = 0u32;
8903 while i < max_iter && x * x + y * y < 4.0 {
8904 let t = x * x - y * y + zx0;
8905 y = 2.0 * x * y + zy0;
8906 x = t;
8907 i += 1;
8908 }
8909 let t = if i == max_iter {
8910 0.0f32
8911 } else {
8912 (i as f32
8913 - (x as f32 * x as f32 + y as f32 * y as f32).ln().ln()
8914 / 2.0f32.ln())
8915 / max_iter as f32
8916 };
8917 let [cr, cg, cb] = tex_palette(&palette, t.clamp(0., 1.));
8918 let (dx, dy) = (tx + col, ty + row);
8919 if dx < bw && dy < bh {
8920 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8921 }
8922 }
8923 }
8924 return Ok(Value::Unit);
8925 },
8926
8927 "tex_julia" | "ลายจูเลีย" | "بافت_ژولیا" | "نسيج_جوليا" | "מרקם_ג'וליה" | "جولیا_ٹیکسچر" => {
8929 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8930 let c_re = self.arg_num(&args, 4, -0.7)?;
8931 let c_im = self.arg_num(&args, 5, 0.27)?;
8932 let max_iter = self.arg_num(&args, 6, 64.0)? as u32;
8933 let palette = self.arg_str(&args, 7, "neon");
8934 let mut gfx = self.gfx.borrow_mut();
8935 let (bw, bh) = (gfx.width, gfx.height);
8936 for row in 0..th {
8937 for col in 0..tw {
8938 let mut zx = (col as f64 / tw as f64 - 0.5) * 3.5;
8939 let mut zy = (row as f64 / th as f64 - 0.5) * 3.5;
8940 let mut i = 0u32;
8941 while i < max_iter && zx * zx + zy * zy < 4.0 {
8942 let t = zx * zx - zy * zy + c_re;
8943 zy = 2.0 * zx * zy + c_im;
8944 zx = t;
8945 i += 1;
8946 }
8947 let t = i as f32 / max_iter as f32;
8948 let [cr, cg, cb] = tex_palette(&palette, t);
8949 let (dx, dy) = (tx + col, ty + row);
8950 if dx < bw && dy < bh {
8951 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8952 }
8953 }
8954 }
8955 return Ok(Value::Unit);
8956 },
8957
8958 "tex_voronoi" | "ลายโวโรนอย" | "بافت_ورونوی" | "نسيج_فورونوي" | "מרקם_וורונוי" | "ورونوئی_ٹیکسچر" => {
8960 let (tx, ty, tw, th) = self.tex_rect(&args)?;
8961 let cells = self.arg_num(&args, 4, 16.0)? as u32;
8962 let seed = self.arg_num(&args, 5, 42.0)? as u32;
8963 let palette = self.arg_str(&args, 6, "rainbow");
8964 let pts: Vec<[f32; 2]> = (0..cells)
8965 .map(|i| {
8966 [
8967 tex_hash(i as i32, 0, seed),
8968 tex_hash(i as i32, 1, seed + 999),
8969 ]
8970 })
8971 .collect();
8972 let mut gfx = self.gfx.borrow_mut();
8973 let (bw, bh) = (gfx.width, gfx.height);
8974 for row in 0..th {
8975 for col in 0..tw {
8976 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
8977 let (min_d, nearest) = pts.iter().enumerate().fold(
8978 (f32::MAX, 0usize),
8979 |(d, idx), (i, &[cx, cy])| {
8980 let dd = (fx - cx).powi(2) + (fy - cy).powi(2);
8981 if dd < d {
8982 (dd, i)
8983 } else {
8984 (d, idx)
8985 }
8986 },
8987 );
8988 let t = (nearest as f32 / cells as f32 + min_d * 4.0) % 1.0;
8989 let [cr, cg, cb] = tex_palette(&palette, t);
8990 let (dx, dy) = (tx + col, ty + row);
8991 if dx < bw && dy < bh {
8992 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
8993 }
8994 }
8995 }
8996 return Ok(Value::Unit);
8997 },
8998
8999 "tex_halftone" | "ลายฮาล์ฟโทน" | "بافت_نیمتن" | "نسيج_نصفي" | "מרקם_חצי_גוון" | "ہاف_ٹون_ٹیکسچر" => {
9001 let (tx, ty, tw, th) = self.tex_rect(&args)?;
9002 let dot_size = self.arg_num(&args, 4, 0.05)? as f32;
9003 let time = self.arg_num(&args, 5, 0.0)? as f32;
9004 let palette = self.arg_str(&args, 6, "rainbow");
9005 let mut gfx = self.gfx.borrow_mut();
9006 let (bw, bh) = (gfx.width, gfx.height);
9007 for row in 0..th {
9008 for col in 0..tw {
9009 let (fx, fy) = (col as f32 / tw as f32, row as f32 / th as f32);
9010 let gx = (fx / dot_size).floor();
9011 let gy = (fy / dot_size).floor();
9012 let lx = (fx / dot_size - gx - 0.5) * 2.0;
9013 let ly = (fy / dot_size - gy - 0.5) * 2.0;
9014 let r = (lx * lx + ly * ly).sqrt();
9015 let t = (gx / (1.0 / dot_size) + time * 0.1) % 1.0;
9016 let a = if r < 0.7 {
9017 ((0.7 - r) / 0.7).clamp(0., 1.)
9018 } else {
9019 0.0
9020 };
9021 if a > 0.0 {
9022 let [cr, cg, cb] = tex_palette(&palette, t);
9023 let (dx, dy) = (tx + col, ty + row);
9024 if dx < bw && dy < bh {
9025 gfx.buffer[dy * bw + dx] = tex_rgb(cr, cg, cb);
9026 }
9027 }
9028 }
9029 }
9030 return Ok(Value::Unit);
9031 },
9032
9033 "set_shade_mode" | "设置着色" | "シェード設定" | "셰이드모드" | "ตั้งการแรเงา" | "تنظیم_حالت_سایهپردازی" | "عيّن_نمط_التظليل" | "קבע_מצב_הצללה" | "شیڈ_موڈ_مقرر_کرو" | "définir_mode_ombrage" | "schattierungsmodus_setzen" | "задать_режим_затенения" =>
9038 {
9039 let m = self.arg_num(&args, 0, 2.0)? as u8;
9040 self.gfx.borrow_mut().shade_mode = m;
9041 return Ok(Value::Unit);
9042 },
9043 "set_cel_bands" | "设置色阶" | "セル段数" | "셀밴드" | "ตั้งระดับสี" | "تنظیم_باندهای_سل" | "عيّن_نطاقات_التظليل" | "קבע_רצועות_הצללה" | "سیل_بینڈز_مقرر_کرو" | "définir_bandes_cel" | "cel_bänder_setzen" | "задать_полосы_cel" =>
9045 {
9046 let n = (self.arg_num(&args, 0, 4.0)? as u32).max(2);
9047 self.gfx.borrow_mut().shade.bands = n;
9048 return Ok(Value::Unit);
9049 },
9050 "set_shadow_color" | "设置阴影色" | "影の色" | "그림자색" | "ตั้งสีเงา" | "تنظیم_رنگ_سایه" | "عيّن_لون_الظل" | "קבע_צבע_צל" | "سایہ_رنگ_مقرر_کرو" | "définir_couleur_ombre" | "schattenfarbe_setzen" | "задать_цвет_тени" =>
9052 {
9053 let r = self.arg_num(&args, 0, 26.)? as f32 / 255.0;
9054 let g = self.arg_num(&args, 1, 33.)? as f32 / 255.0;
9055 let b = self.arg_num(&args, 2, 77.)? as f32 / 255.0;
9056 self.gfx.borrow_mut().shade.shadow = [r, g, b];
9057 return Ok(Value::Unit);
9058 },
9059 #[cfg(not(target_arch = "wasm32"))]
9065 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" | "درهم_رمزنگاری" | "بصمة_تشفير" | "גיבוב_הצפנה" | "خفیہ_ہیش" | "hachage_crypto" | "krypto_hash" | "крипто_хеш" =>
9066 {
9067 let s = self.arg_str(&args, 0, "");
9068 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9069 s.as_bytes(),
9070 ))));
9071 },
9072 #[cfg(target_arch = "wasm32")]
9073 "crypto_hash" | "แฮชเข้ารหัส" | "几何哈希" | "幾何ハッシュ" | "기하해시" | "درهم_رمزنگاری" | "بصمة_تشفير" | "גיבוב_הצפנה" | "خفیہ_ہیش" | "hachage_crypto" | "krypto_hash" | "крипто_хеш" =>
9074 {
9075 let s = self.arg_str(&args, 0, "");
9076 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(
9077 s.as_bytes(),
9078 ))));
9079 },
9080 #[cfg(not(target_arch = "wasm32"))]
9082 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" | "نقاط_گره" | "نقاط_العقدة" | "נקודות_קשר" | "گرہ_پوائنٹس" | "points_nœud" | "knotenpunkte" | "точки_узла" => {
9083 let s = self.arg_str(&args, 0, "");
9084 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9085 let mut out = Vec::with_capacity(shape.points.len() * 3);
9086 for p in &shape.points {
9087 out.push(Value::Number(p[0] as f64));
9088 out.push(Value::Number(p[1] as f64));
9089 out.push(Value::Number(p[2] as f64));
9090 }
9091 return Ok(Value::List(Rc::new(out)));
9092 },
9093 #[cfg(target_arch = "wasm32")]
9094 "knot_points" | "จุดปม" | "结点坐标" | "結び目点" | "매듭점" | "نقاط_گره" | "نقاط_العقدة" | "נקודות_קשר" | "گرہ_پوائنٹس" | "points_nœud" | "knotenpunkte" | "точки_узла" => {
9095 let s = self.arg_str(&args, 0, "");
9096 let shape = ling_crypto::geo::KnotShape::from_bytes(s.as_bytes());
9097 let mut out = Vec::with_capacity(shape.points.len() * 3);
9098 for p in &shape.points {
9099 out.push(Value::Number(p[0] as f64));
9100 out.push(Value::Number(p[1] as f64));
9101 out.push(Value::Number(p[2] as f64));
9102 }
9103 return Ok(Value::List(out.into()));
9104 },
9105 #[cfg(not(target_arch = "wasm32"))]
9106 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" | "برچسب_گره" | "تسمية_العقدة" | "תווית_קשר" | "گرہ_لیبل" | "étiquette_nœud" | "knotenbezeichnung" | "метка_узла" =>
9107 {
9108 let s = self.arg_str(&args, 0, "");
9109 return Ok(Value::Str(
9110 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9111 ));
9112 },
9113 #[cfg(target_arch = "wasm32")]
9114 "knot_label" | "ป้ายปม" | "结点标签" | "結び目ラベル" | "매듭라벨" | "برچسب_گره" | "تسمية_العقدة" | "תווית_קשר" | "گرہ_لیبل" | "étiquette_nœud" | "knotenbezeichnung" | "метка_узла" =>
9115 {
9116 let s = self.arg_str(&args, 0, "");
9117 return Ok(Value::Str(
9118 ling_crypto::geo::KnotShape::from_bytes(s.as_bytes()).label(),
9119 ));
9120 },
9121 #[cfg(not(target_arch = "wasm32"))]
9123 "knot_keygen" | "hybrid_keygen" | "สร้างกุญแจปม" | "生成密钥" | "鍵生成" | "키생성" | "تولید_کلید_گره" | "توليد_مفتاح_العقدة" | "יצירת_מפתח_קשר" | "گرہ_کلید_تخلیق" | "génération_clé_nœud" | "knotenschlüsselerzeugung" | "генерация_ключа_узла" =>
9124 {
9125 self.crypto_ids.push(ling_crypto::KnotIdentity::generate());
9126 return Ok(Value::Number((self.crypto_ids.len() - 1) as f64));
9127 },
9128 #[cfg(not(target_arch = "wasm32"))]
9129 "knot_public" | "hybrid_public" | "กุญแจสาธารณะปม" | "公钥" | "公開鍵" | "공개키" | "کلید_عمومی_گره" | "مفتاح_العقدة_العام" | "מפתח_ציבורי_קשר" | "گرہ_عوامی_کلید" | "clé_publique_nœud" | "knoten_öffentlicher_schlüssel" | "публичный_ключ_узла" =>
9130 {
9131 let h = self.arg_num(&args, 0, 0.0)? as usize;
9132 let pk = self
9133 .crypto_ids
9134 .get(h)
9135 .map(|id| hex_encode(id.public_key()))
9136 .unwrap_or_default();
9137 return Ok(Value::Str(pk));
9138 },
9139 #[cfg(not(target_arch = "wasm32"))]
9141 "knot_encapsulate"
9142 | "hybrid_encapsulate"
9143 | "ห่อกุญแจปม"
9144 | "封装密钥"
9145 | "カプセル化"
9146 | "캡슐화" | "کپسولهسازی_گره" | "تغليف_مفتاح_العقدة" | "עטיפת_קשר" | "گرہ_احاطہ" | "encapsuler_nœud" | "knoten_kapseln" | "инкапсулировать_узел" => {
9147 let pk = hex_decode(&self.arg_str(&args, 0, ""));
9148 match ling_crypto::geo::knot_encapsulate(&pk) {
9149 Ok((ct, ss)) => {
9150 return Ok(Value::List(Rc::new(vec![
9151 Value::Str(hex_encode(&ct)),
9152 Value::Str(hex_encode(&ss)),
9153 ])))
9154 },
9155 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9156 }
9157 },
9158 #[cfg(not(target_arch = "wasm32"))]
9160 "knot_decapsulate"
9161 | "hybrid_decapsulate"
9162 | "แกะกุญแจปม"
9163 | "解封装密钥"
9164 | "カプセル解除"
9165 | "캡슐해제" | "بازکردن_کپسوله_گره" | "فك_تغليف_مفتاح_العقدة" | "פתיחת_עטיפת_קשר" | "گرہ_احاطہ_کھولو" | "décapsuler_nœud" | "knoten_entkapseln" | "декапсулировать_узел" => {
9166 let h = self.arg_num(&args, 0, 0.0)? as usize;
9167 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9168 let ss = self
9169 .crypto_ids
9170 .get(h)
9171 .and_then(|id| id.decapsulate(&ct).ok())
9172 .map(|s| hex_encode(&s))
9173 .unwrap_or_default();
9174 return Ok(Value::Str(ss));
9175 },
9176 #[cfg(not(target_arch = "wasm32"))]
9178 "crypto_seal" | "ผนึก" | "封印" | "封印する" | "봉인" | "مهر_رمزنگاری" | "ختم_تشفير" | "חתימת_הצפנה" | "خفیہ_مہر" | "sceller_crypto" | "krypto_versiegeln" | "запечатать_крипто" => {
9179 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9180 let pt = self.arg_str(&args, 1, "");
9181 match ling_crypto::geo::holo_seal(key, pt.as_bytes()) {
9182 Ok(ct) => return Ok(Value::Str(hex_encode(&ct))),
9183 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9184 }
9185 },
9186 #[cfg(not(target_arch = "wasm32"))]
9187 "crypto_open" | "เปิดผนึก" | "解封" | "封印解除" | "봉인해제" | "بازکردن_مهر" | "فتح_الختم" | "פתיחת_חתימה" | "مہر_کھولو" | "ouvrir_crypto" | "krypto_öffnen" | "открыть_крипто" =>
9188 {
9189 let key = hex_to_32(&self.arg_str(&args, 0, ""));
9190 let ct = hex_decode(&self.arg_str(&args, 1, ""));
9191 match ling_crypto::geo::holo_open(key, &ct) {
9192 Ok(pt) => return Ok(Value::Str(String::from_utf8_lossy(&pt).into_owned())),
9193 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
9194 }
9195 },
9196 #[cfg(not(target_arch = "wasm32"))]
9198 "holo_points" | "จุดโฮโลแกรม" | "全息点" | "ホログラム点" | "홀로그램점" | "نقاط_هولوگرام" | "نقاط_الهولوغرام" | "נקודות_הולוגרמה" | "ہولوگرام_پوائنٹس" | "points_holo" | "holo_punkte" | "точки_голо" =>
9199 {
9200 let s = self.arg_str(&args, 0, "");
9201 let frags = ling_crypto::geo::scatter(s.as_bytes());
9202 let mut out = Vec::with_capacity(frags.len() * 4);
9203 for f in &frags {
9204 for c in f.coord {
9205 out.push(Value::Number(c as f64));
9206 }
9207 }
9208 return Ok(Value::List(Rc::new(out)));
9209 },
9210 #[cfg(not(target_arch = "wasm32"))]
9211 "holo_fragment_count"
9212 | "จำนวนชิ้นโฮโลแกรม"
9213 | "全息碎片数"
9214 | "ホログラム断片数"
9215 | "홀로그램조각수" | "تعداد_قطعات_هولوگرام" | "عدد_شظايا_الهولوغرام" | "מספר_שברי_הולוגרמה" | "ہولوگرام_ٹکڑے_تعداد" | "nombre_fragments_holo" | "holo_fragmentanzahl" | "число_фрагментов_голо" => {
9216 let s = self.arg_str(&args, 0, "");
9217 return Ok(Value::Number(
9218 ling_crypto::geo::scatter(s.as_bytes()).len() as f64
9219 ));
9220 },
9221 #[cfg(not(target_arch = "wasm32"))]
9224 "shake_hex" | "SHAKE哈希" => {
9225 let s = self.arg_str(&args, 0, "");
9226 let len = self.arg_num(&args, 1, 32.0)?.max(0.0) as usize;
9227 return Ok(Value::Str(hex_encode(&ling_crypto::Shake256::hash(s.as_bytes(), len))));
9228 },
9229 #[cfg(not(target_arch = "wasm32"))]
9231 "ed25519_keygen" | "생성서명키" => {
9232 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::generate());
9233 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9234 },
9235 #[cfg(not(target_arch = "wasm32"))]
9240 "ed25519_keygen_from_seed" | "씨앗에서생성서명키" => {
9241 let seed = hex_to_32(&self.arg_str(&args, 0, ""));
9242 self.ed25519_ids.push(ling_crypto::Ed25519Keypair::from_seed(seed));
9243 return Ok(Value::Number((self.ed25519_ids.len() - 1) as f64));
9244 },
9245 #[cfg(not(target_arch = "wasm32"))]
9246 "ed25519_public" | "서명공개키" => {
9247 let h = self.arg_num(&args, 0, 0.0)? as usize;
9248 let pk = self
9249 .ed25519_ids
9250 .get(h)
9251 .map(|kp| hex_encode(&kp.public_key()))
9252 .unwrap_or_default();
9253 return Ok(Value::Str(pk));
9254 },
9255 #[cfg(not(target_arch = "wasm32"))]
9257 "ed25519_sign" | "서명하다" => {
9258 let h = self.arg_num(&args, 0, 0.0)? as usize;
9259 let msg = self.arg_str(&args, 1, "");
9260 let sig = self
9261 .ed25519_ids
9262 .get(h)
9263 .map(|kp| hex_encode(&kp.sign(msg.as_bytes())))
9264 .unwrap_or_default();
9265 return Ok(Value::Str(sig));
9266 },
9267 #[cfg(not(target_arch = "wasm32"))]
9269 "ed25519_verify" | "서명확인" => {
9270 let pk_hex = self.arg_str(&args, 0, "");
9271 let msg = self.arg_str(&args, 1, "");
9272 let sig_hex = self.arg_str(&args, 2, "");
9273 let pk_bytes = hex_decode(&pk_hex);
9274 let sig_bytes = hex_decode(&sig_hex);
9275 let ok = (|| {
9276 let pk: [u8; 32] = pk_bytes.try_into().ok()?;
9277 let sig: [u8; 64] = sig_bytes.try_into().ok()?;
9278 Some(ling_crypto::Ed25519Keypair::verify(&pk, msg.as_bytes(), &sig).is_ok())
9279 })()
9280 .unwrap_or(false);
9281 return Ok(Value::Bool(ok));
9282 },
9283 #[cfg(not(target_arch = "wasm32"))]
9286 "password_hash" | "비밀번호해시" => {
9287 let pw = self.arg_str(&args, 0, "");
9288 let hash = ling_crypto::Argon2idParams::default()
9289 .hash_password(pw.as_bytes())
9290 .unwrap_or_default();
9291 return Ok(Value::Str(hash));
9292 },
9293 #[cfg(not(target_arch = "wasm32"))]
9294 "password_verify" | "비밀번호확인" => {
9295 let pw = self.arg_str(&args, 0, "");
9296 let hash = self.arg_str(&args, 1, "");
9297 let ok = ling_crypto::Argon2idParams::verify_password(pw.as_bytes(), &hash).is_ok();
9298 return Ok(Value::Bool(ok));
9299 },
9300 #[cfg(not(target_arch = "wasm32"))]
9303 "random_hex" | "무작위16진수" => {
9304 use rand::RngCore;
9305 let n = self.arg_num(&args, 0, 16.0)?.max(0.0) as usize;
9306 let mut buf = vec![0u8; n];
9307 rand::rngs::OsRng.fill_bytes(&mut buf);
9308 return Ok(Value::Str(hex_encode(&buf)));
9309 },
9310 #[cfg(not(target_arch = "wasm32"))]
9314 "base64_encode" | "base64인코딩" => {
9315 use base64::Engine as _;
9316 let s = self.arg_str(&args, 0, "");
9317 return Ok(Value::Str(
9318 base64::engine::general_purpose::STANDARD.encode(s.as_bytes()),
9319 ));
9320 },
9321 #[cfg(not(target_arch = "wasm32"))]
9325 "base64_decode_to_file" | "base64파일로저장" => {
9326 use base64::Engine as _;
9327 let b64 = self.arg_str(&args, 0, "");
9328 let path = self.arg_str(&args, 1, "");
9329 let b64 = b64
9330 .split(',')
9331 .next_back()
9332 .unwrap_or(&b64); let ok = base64::engine::general_purpose::STANDARD
9334 .decode(b64)
9335 .ok()
9336 .and_then(|bytes| std::fs::write(&path, bytes).ok())
9337 .is_some();
9338 return Ok(Value::Bool(ok));
9339 },
9340 #[cfg(not(target_arch = "wasm32"))]
9345 "qr_svg" | "QR코드" => {
9346 let text = self.arg_str(&args, 0, "");
9347 let svg = qrcode::QrCode::new(text.as_bytes())
9348 .map(|code| {
9349 code.render::<qrcode::render::svg::Color>()
9350 .min_dimensions(240, 240)
9351 .dark_color(qrcode::render::svg::Color("#1a0f3d"))
9352 .light_color(qrcode::render::svg::Color("#ffffff"))
9353 .build()
9354 })
9355 .unwrap_or_default();
9356 return Ok(Value::Str(svg));
9357 },
9358 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9361 "zip_files" | "압축파일" => {
9362 let paths = match args.first() {
9363 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9364 _ => Vec::new(),
9365 };
9366 let out_path = self.arg_str(&args, 1, "out.zip");
9367 let ok = (|| -> std::io::Result<()> {
9368 let file = std::fs::File::create(&out_path)?;
9369 let mut writer = zip::ZipWriter::new(file);
9370 let options: zip::write::FileOptions<'_, ()> = zip::write::FileOptions::default()
9371 .compression_method(zip::CompressionMethod::Deflated);
9372 for p in &paths {
9373 let name = std::path::Path::new(p)
9374 .file_name()
9375 .map(|n| n.to_string_lossy().into_owned())
9376 .unwrap_or_else(|| p.clone());
9377 let bytes = std::fs::read(p)?;
9378 writer.start_file(name, options)?;
9379 std::io::Write::write_all(&mut writer, &bytes)?;
9380 }
9381 writer.finish()?;
9382 Ok(())
9383 })()
9384 .is_ok();
9385 return Ok(Value::Bool(ok));
9386 },
9387 #[cfg(all(not(target_arch = "wasm32"), feature = "web"))]
9394 "pdf_from_images" | "PDF来自图片" => {
9395 let paths = match args.first() {
9396 Some(Value::List(l)) => l.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
9397 _ => Vec::new(),
9398 };
9399 let out_path = self.arg_str(&args, 1, "out.pdf");
9400 let ok = build_pdf_from_images(&paths, &out_path).is_ok();
9401 return Ok(Value::Bool(ok));
9402 },
9403
9404 "ease" => {
9408 let name = self.arg_str(&args, 0, "ease");
9409 let t = self.arg_num(&args, 1, 0.0)? as f32;
9410 return Ok(Value::Number(
9411 ling_ui::Easing::from_name(&name).apply(t) as f64
9412 ));
9413 },
9414
9415 "tween" | "补间" | "補間" | "트윈" | "แทรกค่า" | "میانفریم" | "تدرج_حركي" | "טווין" | "ٹوئین" | "твин" => {
9420 let a = self.arg_num(&args, 0, 0.0)?;
9421 let b = self.arg_num(&args, 1, 0.0)?;
9422 let t = self.arg_num(&args, 2, 0.0)?.clamp(0.0, 1.0);
9423 return Ok(Value::Number(a + (b - a) * t));
9424 },
9425 "tween_ease" | "缓动补间" | "緩和補間" | "이징트윈" | "แทรกนุ่ม" | "میانفریم_نرم" | "تدرج_ناعم_حركي" | "טווין_חלק" | "ٹوئین_ایز" | "tween_lisse" | "tween_glättung" | "твин_плавность" =>
9426 {
9427 let a = self.arg_num(&args, 0, 0.0)? as f32;
9428 let b = self.arg_num(&args, 1, 0.0)? as f32;
9429 let t = self.arg_num(&args, 2, 0.0)? as f32;
9430 let kind = self.arg_str(&args, 3, "linear");
9431 let e = ling_animation::EaseFunction::from_name(&kind);
9432 return Ok(Value::Number(
9433 ling_animation::ease::tween_ease(&a, &b, t, e) as f64,
9434 ));
9435 },
9436 "breathe" | "呼吸" | "호흡" | "หายใจ" | "تنفس" | "تنفس" | "נשימה" | "سانس" | "respirer" | "atmen" | "дышать" => {
9438 let t = self.arg_num(&args, 0, 0.0)? as f32;
9439 let rate = self.arg_num(&args, 1, 1.0)? as f32;
9440 let depth = self.arg_num(&args, 2, 0.1)? as f32;
9441 return Ok(Value::Number(
9442 ling_animation::scalar::breathe(t, rate, depth) as f64,
9443 ));
9444 },
9445 "wobble" | "摆动" | "揺れ" | "흔들림" | "โยก" | "نوسان" | "تذبذب" | "תנודה" | "لرزش" | "osciller" | "wackeln" | "покачивание" => {
9446 let t = self.arg_num(&args, 0, 0.0)? as f32;
9447 let freq = self.arg_num(&args, 1, 1.0)? as f32;
9448 let amp = self.arg_num(&args, 2, 1.0)? as f32;
9449 let phase = self.arg_num(&args, 3, 0.0)? as f32;
9450 return Ok(Value::Number(
9451 ling_animation::scalar::wobble(t, freq, amp, phase) as f64,
9452 ));
9453 },
9454 "gait_phase" | "步相" | "歩相" | "걸음위상" | "เฟสก้าว" | "فاز_گام" | "طور_المشية" | "שלב_הליכה" | "چال_مرحلہ" | "phase_démarche" | "gangphase" | "фаза_походки" => {
9455 let t = self.arg_num(&args, 0, 0.0)? as f32;
9456 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9457 return Ok(Value::Number(
9458 ling_animation::scalar::gait_phase(t, speed) as f64
9459 ));
9460 },
9461 "gait_swing" | "步摆" | "歩振り" | "걸음흔들" | "ก้าวแกว่ง" | "نوسان_گام" | "أرجحة_المشية" | "נדנוד_הליכה" | "چال_جھولا" | "balancement_démarche" | "gangschwung" | "мах_походки" =>
9462 {
9463 let t = self.arg_num(&args, 0, 0.0)? as f32;
9464 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9465 let stride = self.arg_num(&args, 2, 1.0)? as f32;
9466 return Ok(Value::Number(
9467 ling_animation::scalar::gait_swing(t, speed, stride) as f64,
9468 ));
9469 },
9470 "gait_lift" | "抬脚" | "足上げ" | "발들기" | "ยกเท้า" | "بلندشدن_گام" | "رفع_المشية" | "הרמת_הליכה" | "چال_اٹھاؤ" | "levée_démarche" | "ganghub" | "подъём_походки" => {
9471 let t = self.arg_num(&args, 0, 0.0)? as f32;
9472 let speed = self.arg_num(&args, 1, 1.0)? as f32;
9473 let height = self.arg_num(&args, 2, 1.0)? as f32;
9474 return Ok(Value::Number(
9475 ling_animation::scalar::gait_lift(t, speed, height) as f64,
9476 ));
9477 },
9478 "spring_to" | "弹向" | "バネ寄せ" | "스프링이동" | "สปริงไป" | "فنر_بهسوی" | "نابض_إلى" | "קפיץ_אל" | "اسپرنگ_تک" | "ressort_vers" | "feder_zu" | "пружина_к" =>
9479 {
9480 let pos = self.arg_num(&args, 0, 0.0)? as f32;
9481 let vel = self.arg_num(&args, 1, 0.0)? as f32;
9482 let target = self.arg_num(&args, 2, 0.0)? as f32;
9483 let stiffness = self.arg_num(&args, 3, 120.0)? as f32;
9484 let damping = self.arg_num(&args, 4, 14.0)? as f32;
9485 let dt = self.arg_num(&args, 5, 1.0 / 60.0)? as f32;
9486 let (np, nv) =
9487 ling_animation::scalar::spring_step(pos, vel, target, stiffness, damping, dt);
9488 return Ok(Value::List(Rc::new(vec![
9489 Value::Number(np as f64),
9490 Value::Number(nv as f64),
9491 ])));
9492 },
9493 "ik2" | "反解" | "逆運動" | "역운동" | "ไอเค2" | "سینماتیک_معکوس2" | "حركية_عكسية2" | "קינמטיקה_הפוכה2" | "آئی_کے2" | "cinématique_inverse2" | "inverse_kinematik2" | "обратная_кинематика2" => {
9494 let l1 = self.arg_num(&args, 0, 1.0)? as f32;
9495 let l2 = self.arg_num(&args, 1, 1.0)? as f32;
9496 let tx = self.arg_num(&args, 2, 0.0)? as f32;
9497 let ty = self.arg_num(&args, 3, 0.0)? as f32;
9498 let (sh, el) = ling_animation::scalar::two_bone_ik(l1, l2, tx, ty);
9499 return Ok(Value::List(Rc::new(vec![
9500 Value::Number(sh as f64),
9501 Value::Number(el as f64),
9502 ])));
9503 },
9504 "gear_couple" | "齿轮联动" | "歯車連動" | "기어연동" | "เฟืองทด" | "جفت_چرخدنده" | "اقتران_التروس" | "צימוד_גלגלי_שיניים" | "گیئر_جوڑا" | "accoupler_engrenage" | "zahnrad_koppeln" | "сцепить_шестерни" =>
9506 {
9507 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9508 let ti = self.arg_num(&args, 1, 1.0)? as f32;
9509 let to = self.arg_num(&args, 2, 1.0)? as f32;
9510 return Ok(Value::Number(
9511 ling_animation::scalar::gear(angle, ti, to) as f64
9512 ));
9513 },
9514 "gear_train" | "齿轮组" | "歯車列" | "기어열" | "ชุดเฟือง" | "مجموعه_چرخدنده" | "قطار_التروس" | "שרשרת_גלגלי_שיניים" | "گیئر_ٹرین" | "train_engrenages" | "zahnradgetriebe" | "передача_шестерён" => {
9515 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9516 let teeth: Vec<f32> = match args.get(1) {
9517 Some(Value::List(items)) => items
9518 .iter()
9519 .filter_map(|v| {
9520 if let Value::Number(n) = v {
9521 Some(*n as f32)
9522 } else {
9523 None
9524 }
9525 })
9526 .collect(),
9527 _ => Vec::new(),
9528 };
9529 let out = ling_animation::mechanism::gear_train(angle, &teeth);
9530 return Ok(Value::List(Rc::new(
9531 out.into_iter().map(|a| Value::Number(a as f64)).collect(),
9532 )));
9533 },
9534 "cam_lift" | "凸轮升程" | "カム揚程" | "캠리프트" | "ยกลูกเบี้ยว" | "بلندشدن_بادامک" | "رفع_الكامة" | "הרמת_קאם" | "کیم_اٹھاؤ" | "levée_came" | "nockenhub" | "подъём_кулачка" =>
9535 {
9536 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9537 let lift = self.arg_num(&args, 1, 1.0)? as f32;
9538 return Ok(Value::Number(
9539 ling_animation::scalar::cam_lift(angle, lift) as f64
9540 ));
9541 },
9542 "piston" | "活塞" | "ピストン" | "피스톤" | "ลูกสูบ" | "پیستون" | "مكبس" | "בוכנה" | "پسٹن" | "kolben" | "поршень" => {
9543 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9544 let crank = self.arg_num(&args, 1, 1.0)? as f32;
9545 let rod = self.arg_num(&args, 2, 2.0)? as f32;
9546 return Ok(Value::Number(
9547 ling_animation::scalar::piston(angle, crank, rod) as f64,
9548 ));
9549 },
9550 "rack" | "齿条" | "ラック" | "랙" | "แร็ค" | "زبانهدنده" | "سكة_مسننة" | "מוט_שיניים" | "ریک" | "crémaillère" | "zahnstange" | "рейка" => {
9551 let angle = self.arg_num(&args, 0, 0.0)? as f32;
9552 let radius = self.arg_num(&args, 1, 1.0)? as f32;
9553 return Ok(Value::Number(
9554 ling_animation::scalar::rack(angle, radius) as f64
9555 ));
9556 },
9557 #[cfg(not(target_arch = "wasm32"))]
9558 "mouse_x" => {
9559 let gfx = self.gfx.borrow();
9560 let v = gfx
9561 .window
9562 .as_ref()
9563 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9564 .map(|p| p.0 as f64)
9565 .unwrap_or(0.0);
9566 return Ok(Value::Number(v));
9567 },
9568 #[cfg(target_arch = "wasm32")]
9569 "mouse_x" => {
9570 return Ok(Value::Number(crate::gfx::wasm_mouse_x() as f64));
9571 },
9572 #[cfg(not(target_arch = "wasm32"))]
9573 "mouse_y" => {
9574 let gfx = self.gfx.borrow();
9575 let v = gfx
9576 .window
9577 .as_ref()
9578 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
9579 .map(|p| p.1 as f64)
9580 .unwrap_or(0.0);
9581 return Ok(Value::Number(v));
9582 },
9583 #[cfg(target_arch = "wasm32")]
9584 "mouse_y" => {
9585 return Ok(Value::Number(crate::gfx::wasm_mouse_y() as f64));
9586 },
9587 #[cfg(not(target_arch = "wasm32"))]
9588 "mouse_down" => {
9589 let mut gfx = self.gfx.borrow_mut();
9590 let d = !gfx.input_suppressed()
9591 && gfx
9592 .window
9593 .as_ref()
9594 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
9595 .unwrap_or(false);
9596 return Ok(Value::Bool(d));
9597 },
9598 #[cfg(target_arch = "wasm32")]
9599 "mouse_down" => {
9600 return Ok(Value::Bool(crate::gfx::wasm_mouse_down()));
9601 },
9602 #[cfg(not(target_arch = "wasm32"))]
9603 "mouse_down_right" | "เมาส์ขวา" | "ماوس_راست_فشرده" | "الفأرة_اليمنى_مضغوطة" | "עכבר_ימני_לחוץ" | "دایاں_ماؤس_دبا_ہوا" => {
9604 let mut gfx = self.gfx.borrow_mut();
9605 let d = !gfx.input_suppressed()
9606 && gfx
9607 .window
9608 .as_ref()
9609 .map(|w| w.get_mouse_down(minifb::MouseButton::Right))
9610 .unwrap_or(false);
9611 return Ok(Value::Bool(d));
9612 },
9613 #[cfg(target_arch = "wasm32")]
9614 "mouse_down_right" | "เมาส์ขวา" | "ماوس_راست_فشرده" | "الفأرة_اليمنى_مضغوطة" | "עכבר_ימני_לחוץ" | "دایاں_ماؤس_دبا_ہوا" => {
9615 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_right()));
9616 },
9617 #[cfg(not(target_arch = "wasm32"))]
9618 "mouse_down_middle" | "เมาส์กลาง" | "ماوس_وسط_فشرده" | "الفأرة_الوسطى_مضغوطة" | "עכבר_אמצעי_לחוץ" | "درمیانی_ماؤس_دبا_ہوا" => {
9619 let mut gfx = self.gfx.borrow_mut();
9620 let d = !gfx.input_suppressed()
9621 && gfx
9622 .window
9623 .as_ref()
9624 .map(|w| w.get_mouse_down(minifb::MouseButton::Middle))
9625 .unwrap_or(false);
9626 return Ok(Value::Bool(d));
9627 },
9628 #[cfg(target_arch = "wasm32")]
9629 "mouse_down_middle" | "เมาส์กลาง" | "ماوس_وسط_فشرده" | "الفأرة_الوسطى_مضغوطة" | "עכבר_אמצעי_לחוץ" | "درمیانی_ماؤس_دبا_ہوا" => {
9630 return Ok(Value::Bool(crate::gfx::wasm_mouse_down_middle()));
9631 },
9632 #[cfg(not(target_arch = "wasm32"))]
9633 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" | "ناحیه_فعال" | "منطقة_ساخنة" | "אזור_חם" | "ہاٹ_زون" | "survol_ui" | "ui_hover" | "ui_наведение" =>
9634 {
9635 let x = self.arg_num(&args, 0, 0.0)? as f32;
9636 let y = self.arg_num(&args, 1, 0.0)? as f32;
9637 let w = self.arg_num(&args, 2, 0.0)? as f32;
9638 let h = self.arg_num(&args, 3, 0.0)? as f32;
9639 let gfx = self.gfx.borrow();
9640 let (mx, my) = gfx
9641 .window
9642 .as_ref()
9643 .and_then(|win| win.get_mouse_pos(minifb::MouseMode::Clamp))
9644 .unwrap_or((0.0, 0.0));
9645 return Ok(Value::Bool(ling_ui::holo::hit_rect(mx, my, x, y, w, h)));
9646 },
9647 #[cfg(target_arch = "wasm32")]
9648 "ui_hot" | "热区" | "ホットエリア" | "핫존" | "พื้นที่สัมผัส" | "ناحیه_فعال" | "منطقة_ساخنة" | "אזור_חם" | "ہاٹ_زون" | "survol_ui" | "ui_hover" | "ui_наведение" =>
9649 {
9650 return Ok(Value::Bool(false));
9651 },
9652 "ui_text" | "界面文字" | "UI文字" | "UI텍스트" | "ข้อความหน้าจอ" | "متن_رابط" | "نص_الواجهة" | "טקסט_ממשק" | "یو_آئی_متن" | "texte_ui" | "ui_beschriftung" | "ui_текст" =>
9654 {
9655 let x = self.arg_num(&args, 0, 0.0)? as f32;
9656 let y = self.arg_num(&args, 1, 0.0)? as f32;
9657 let scale = self.arg_num(&args, 2, 16.0)? as f32;
9658 let s = self.arg_str(&args, 3, "");
9659 let segs = ling_ui::holo::text_lines(&s, x, y, scale * 0.62, scale, scale * 0.24);
9660 let mut gfx = self.gfx.borrow_mut();
9661 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
9662 for sg in segs {
9663 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
9664 }
9665 return Ok(Value::Unit);
9666 },
9667 #[cfg(not(target_arch = "wasm32"))]
9670 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" | "بارگذاری_فونت" | "تحميل_الخط" | "טעינת_גופן" | "فونٹ_لوڈ" | "charger_police" | "schriftart_laden" | "загрузить_шрифт" =>
9671 {
9672 let path = self.arg_str(&args, 0, "");
9673 let weight = match self.arg_num(&args, 1, 0.0)? {
9675 w if w > 0.0 => Some(w as f32),
9676 _ => None,
9677 };
9678 let mut loaded = ling_graphics::VectorFont::from_path_weight(&path, weight);
9680 if loaded.is_err() {
9681 if let Some(dir) = &self.source_dir {
9682 let joined = dir.join(&path);
9683 loaded = ling_graphics::VectorFont::from_path_weight(
9684 &joined.to_string_lossy(),
9685 weight,
9686 );
9687 }
9688 }
9689 match loaded {
9690 Ok(f) => {
9691 let id = self.fonts.len();
9692 self.fonts.push(f);
9693 return Ok(Value::Number(id as f64));
9694 },
9695 Err(e) => {
9696 eprintln!("font_load failed ({path}): {e}");
9697 return Ok(Value::Number(-1.0));
9698 },
9699 }
9700 },
9701 #[cfg(target_arch = "wasm32")]
9702 "font_load" | "โหลดฟอนต์" | "加载字体" | "フォント読込" | "글꼴로드" | "بارگذاری_فونت" | "تحميل_الخط" | "טעינת_גופן" | "فونٹ_لوڈ" | "charger_police" | "schriftart_laden" | "загрузить_шрифт" =>
9703 {
9704 return Ok(Value::Number(-1.0));
9707 },
9708 #[cfg(not(target_arch = "wasm32"))]
9713 "image_load" =>
9714 {
9715 let path = self.arg_str(&args, 0, "");
9716 let mut loaded = image::open(&path);
9717 if loaded.is_err() {
9718 if let Some(dir) = &self.source_dir {
9719 let joined = dir.join(&path);
9720 loaded = image::open(&joined);
9721 }
9722 }
9723 match loaded {
9724 Ok(img) => {
9725 let id = self.images.len();
9726 self.images.push(img.to_rgba8());
9727 return Ok(Value::Number(id as f64));
9728 },
9729 Err(e) => {
9730 eprintln!("image_load failed ({path}): {e}");
9731 return Ok(Value::Number(-1.0));
9732 },
9733 }
9734 },
9735 #[cfg(target_arch = "wasm32")]
9736 "image_load" =>
9737 {
9738 return Ok(Value::Number(-1.0));
9740 },
9741 "image_width" =>
9742 {
9743 let id = self.arg_num(&args, 0, -1.0)? as i64;
9744 if id >= 0 && (id as usize) < self.images.len() {
9745 return Ok(Value::Number(self.images[id as usize].width() as f64));
9746 }
9747 return Ok(Value::Number(0.0));
9748 },
9749 "image_height" =>
9750 {
9751 let id = self.arg_num(&args, 0, -1.0)? as i64;
9752 if id >= 0 && (id as usize) < self.images.len() {
9753 return Ok(Value::Number(self.images[id as usize].height() as f64));
9754 }
9755 return Ok(Value::Number(0.0));
9756 },
9757 "image_pixel_r" | "image_pixel_g" | "image_pixel_b" | "image_pixel_a" =>
9758 {
9759 let id = self.arg_num(&args, 0, -1.0)? as i64;
9760 let px = self.arg_num(&args, 1, 0.0)? as i64;
9761 let py = self.arg_num(&args, 2, 0.0)? as i64;
9762 if id >= 0 && (id as usize) < self.images.len() {
9763 let img = &self.images[id as usize];
9764 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
9765 let p = img.get_pixel(px as u32, py as u32);
9766 let ch = match name {
9767 "image_pixel_r" => p[0],
9768 "image_pixel_g" => p[1],
9769 "image_pixel_b" => p[2],
9770 _ => p[3],
9771 };
9772 return Ok(Value::Number(ch as f64));
9773 }
9774 }
9775 return Ok(Value::Number(0.0));
9776 },
9777 "image_new" =>
9786 {
9787 let w = self.arg_num(&args, 0, 1.0)?.max(1.0) as u32;
9788 let h = self.arg_num(&args, 1, 1.0)?.max(1.0) as u32;
9789 let id = self.images.len();
9790 self.images.push(image::RgbaImage::new(w, h));
9791 return Ok(Value::Number(id as f64));
9792 },
9793 "image_set_pixel" =>
9798 {
9799 let id = self.arg_num(&args, 0, -1.0)? as i64;
9800 let px = self.arg_num(&args, 1, 0.0)? as i64;
9801 let py = self.arg_num(&args, 2, 0.0)? as i64;
9802 let r = self.arg_num(&args, 3, 0.0)?.clamp(0.0, 255.0) as u8;
9803 let g = self.arg_num(&args, 4, 0.0)?.clamp(0.0, 255.0) as u8;
9804 let b = self.arg_num(&args, 5, 0.0)?.clamp(0.0, 255.0) as u8;
9805 let a = self.arg_num(&args, 6, 255.0)?.clamp(0.0, 255.0) as u8;
9806 if id >= 0 && (id as usize) < self.images.len() {
9807 let img = &mut self.images[id as usize];
9808 if px >= 0 && py >= 0 && (px as u32) < img.width() && (py as u32) < img.height() {
9809 img.put_pixel(px as u32, py as u32, image::Rgba([r, g, b, a]));
9810 }
9811 }
9812 return Ok(Value::Unit);
9813 },
9814 #[cfg(not(target_arch = "wasm32"))]
9822 "image_save" =>
9823 {
9824 let id = self.arg_num(&args, 0, -1.0)? as i64;
9825 let path = self.arg_str(&args, 1, "");
9826 if id >= 0 && (id as usize) < self.images.len() {
9827 if let Some(parent) = std::path::Path::new(&path).parent() {
9828 if !parent.as_os_str().is_empty() {
9829 let _ = std::fs::create_dir_all(parent);
9830 }
9831 }
9832 if self.images[id as usize].save(&path).is_ok() {
9833 return Ok(Value::Number(1.0));
9834 }
9835 }
9836 return Ok(Value::Number(-1.0));
9837 },
9838 #[cfg(target_arch = "wasm32")]
9839 "image_save" =>
9840 {
9841 return Ok(Value::Number(-1.0));
9842 },
9843 #[cfg(not(target_arch = "wasm32"))]
9853 "image_draw" =>
9854 {
9855 let id = self.arg_num(&args, 0, -1.0)? as i64;
9856 let dx = self.arg_num(&args, 1, 0.0)? as i32;
9857 let dy = self.arg_num(&args, 2, 0.0)? as i32;
9858 let dw = self.arg_num(&args, 3, 0.0)?.max(0.0) as i32;
9859 let dh = self.arg_num(&args, 4, 0.0)?.max(0.0) as i32;
9860 if id >= 0 && (id as usize) < self.images.len() && dw > 0 && dh > 0 {
9861 let img = &self.images[id as usize];
9862 let sw = img.width() as i32;
9863 let sh = img.height() as i32;
9864 if sw > 0 && sh > 0 {
9865 let mut gfx = self.gfx.borrow_mut();
9866 let (fw, fh) = (gfx.width as i32, gfx.height as i32);
9867 for py in 0..dh {
9868 let ty = dy + py;
9869 if ty < 0 || ty >= fh {
9870 continue;
9871 }
9872 let sy = (py * sh / dh).clamp(0, sh - 1) as u32;
9873 for px in 0..dw {
9874 let tx = dx + px;
9875 if tx < 0 || tx >= fw {
9876 continue;
9877 }
9878 let sx = (px * sw / dw).clamp(0, sw - 1) as u32;
9879 let p = img.get_pixel(sx, sy);
9880 let a = p[3] as u32;
9881 if a == 0 {
9882 continue;
9883 }
9884 let idx = ty as usize * gfx.width + tx as usize;
9885 if a >= 255 {
9886 gfx.buffer[idx] =
9887 ((p[0] as u32) << 16) | ((p[1] as u32) << 8) | (p[2] as u32);
9888 } else {
9889 let bg = gfx.buffer[idx];
9890 let br = (bg >> 16) & 0xff;
9891 let bg_g = (bg >> 8) & 0xff;
9892 let bb = bg & 0xff;
9893 let r = (p[0] as u32 * a + br * (255 - a)) / 255;
9894 let g = (p[1] as u32 * a + bg_g * (255 - a)) / 255;
9895 let b = (p[2] as u32 * a + bb * (255 - a)) / 255;
9896 gfx.buffer[idx] = (r << 16) | (g << 8) | b;
9897 }
9898 }
9899 }
9900 }
9901 }
9902 return Ok(Value::Unit);
9903 },
9904 #[cfg(target_arch = "wasm32")]
9905 "image_draw" =>
9906 {
9907 return Ok(Value::Unit);
9908 },
9909 #[cfg(not(target_arch = "wasm32"))]
9912 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" | "متن_فونت" | "نص_الخط" | "טקסט_גופן" | "فونٹ_متن" | "texte_police" | "schriftart_text" | "текст_шрифт" =>
9913 {
9914 let id = self.arg_num(&args, 0, 0.0)? as i64;
9915 let x = self.arg_num(&args, 1, 0.0)? as f32;
9916 let y = self.arg_num(&args, 2, 0.0)? as f32;
9917 let px = self.arg_num(&args, 3, 16.0)? as f32;
9918 let s = self.arg_str(&args, 4, "");
9919 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
9920 let strokes = self.font_layout_2d(id as usize, x, y, px, &s);
9921 let mut gfx = self.gfx.borrow_mut();
9922 let (w, h, color, add, aa) =
9923 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
9924 for pl in &strokes {
9925 for seg in pl.windows(2) {
9926 if aa {
9927 crate::gfx::raster::draw_line_aa(
9928 &mut gfx.buffer,
9929 w,
9930 h,
9931 color,
9932 add,
9933 seg[0][0],
9934 seg[0][1],
9935 seg[1][0],
9936 seg[1][1],
9937 );
9938 } else {
9939 crate::gfx::raster::draw_line(
9940 &mut gfx.buffer,
9941 w,
9942 h,
9943 color,
9944 seg[0][0],
9945 seg[0][1],
9946 seg[1][0],
9947 seg[1][1],
9948 );
9949 }
9950 }
9951 }
9952 }
9953 return Ok(Value::Unit);
9954 },
9955 #[cfg(target_arch = "wasm32")]
9956 "font_text" | "ข้อความฟอนต์" | "字体文本" | "フォント文字" | "글꼴텍스트" | "متن_فونت" | "نص_الخط" | "טקסט_גופן" | "فونٹ_متن" | "texte_police" | "schriftart_text" | "текст_шрифт" =>
9957 {
9958 return Ok(Value::Unit);
9959 },
9960 #[cfg(not(target_arch = "wasm32"))]
9963 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" | "پرکردن_متن_فونت" | "تعبئة_نص_الخط" | "מילוי_טקסט_גופן" | "فونٹ_متن_بھرو" | "remplir_texte_police" | "schriftart_text_füllen" | "заполнить_текст_шрифт" =>
9964 {
9965 let id = self.arg_num(&args, 0, 0.0)? as i64;
9966 let x = self.arg_num(&args, 1, 0.0)? as f32;
9967 let y = self.arg_num(&args, 2, 0.0)? as f32;
9968 let px = self.arg_num(&args, 3, 16.0)? as f32;
9969 let s = self.arg_str(&args, 4, "");
9970 if id >= 0 && (id as usize) < self.fonts.len() && px > 0.0 {
9971 let glyphs = self.font_layout_2d_glyphs(id as usize, x, y, px, &s);
9973 let mut gfx = self.gfx.borrow_mut();
9974 let (w, h, color, add, aa) =
9975 (gfx.width, gfx.height, gfx.color, gfx.blend == 1, gfx.font_antialias);
9976 for contours in &glyphs {
9977 if aa {
9978 crate::gfx::raster::fill_contours_aa(
9979 &mut gfx.buffer,
9980 w,
9981 h,
9982 color,
9983 add,
9984 contours,
9985 );
9986 } else {
9987 crate::gfx::raster::fill_contours(
9988 &mut gfx.buffer,
9989 w,
9990 h,
9991 color,
9992 add,
9993 contours,
9994 );
9995 }
9996 }
9997 }
9998 return Ok(Value::Unit);
9999 },
10000 #[cfg(target_arch = "wasm32")]
10001 "font_text_fill" | "เติมฟอนต์" | "填充字体" | "フォント塗り" | "글꼴채움" | "پرکردن_متن_فونت" | "تعبئة_نص_الخط" | "מילוי_טקסט_גופן" | "فونٹ_متن_بھرو" | "remplir_texte_police" | "schriftart_text_füllen" | "заполнить_текст_шрифт" =>
10002 {
10003 return Ok(Value::Unit);
10004 },
10005 #[cfg(not(target_arch = "wasm32"))]
10009 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" | "متن_فونت_سهبعدی" | "نص_خط_ثلاثي_الأبعاد" | "טקסט_גופן_תלת_ממדי" | "تھری_ڈی_فونٹ_متن" | "texte_police_3d" | "schriftart_text_3d" | "текст_шрифт_3d" =>
10010 {
10011 let id = self.arg_num(&args, 0, 0.0)? as i64;
10012 let cx = self.arg_num(&args, 1, 0.0)? as f32;
10013 let cy = self.arg_num(&args, 2, 0.0)? as f32;
10014 let cz = self.arg_num(&args, 3, 0.0)? as f32;
10015 let ux = self.arg_num(&args, 4, 1.0)? as f32;
10016 let uy = self.arg_num(&args, 5, 0.0)? as f32;
10017 let uz = self.arg_num(&args, 6, 0.0)? as f32;
10018 let vx = self.arg_num(&args, 7, 0.0)? as f32;
10019 let vy = self.arg_num(&args, 8, 1.0)? as f32;
10020 let vz = self.arg_num(&args, 9, 0.0)? as f32;
10021 let size = self.arg_num(&args, 10, 1.0)? as f32;
10022 let s = self.arg_str(&args, 11, "");
10023 let fill_rows = self.arg_num(&args, 12, 0.0)? as i32;
10027 if id >= 0 && (id as usize) < self.fonts.len() && size > 0.0 {
10028 let font = &mut self.fonts[id as usize];
10030 let asc = font.ascent();
10031 let mut pen = 0.0f32;
10032 let mut lines: Vec<[f32; 6]> = Vec::new();
10033 for ch in s.chars() {
10034 let go = font.glyph_outline(ch, 0.01);
10035 let map = |p: [f32; 2], pen: f32| {
10036 let a = pen + p[0];
10037 let b = p[1] - asc; [
10039 cx + a * size * ux + b * size * vx,
10040 cy + a * size * uy + b * size * vy,
10041 cz + a * size * uz + b * size * vz,
10042 ]
10043 };
10044 for pl in &go.polylines {
10045 for seg in pl.windows(2) {
10046 let p0 = map(seg[0], pen);
10047 let p1 = map(seg[1], pen);
10048 lines.push([p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]]);
10049 }
10050 }
10051 if fill_rows > 0 {
10052 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
10056 for pl in &go.polylines {
10057 for p in pl {
10058 ymin = ymin.min(p[1]);
10059 ymax = ymax.max(p[1]);
10060 }
10061 }
10062 if ymax > ymin {
10063 for r in 0..fill_rows {
10064 let y =
10065 ymin + (r as f32 + 0.5) * (ymax - ymin) / fill_rows as f32;
10066 let mut xs: Vec<f32> = Vec::new();
10067 for pl in &go.polylines {
10068 let n = pl.len();
10069 if n < 2 {
10070 continue;
10071 }
10072 for k in 0..n {
10073 let p0 = pl[k];
10074 let p1 = pl[(k + 1) % n];
10075 if k + 1 == n
10076 && (p1[0] - p0[0]).abs() < 1e-6
10077 && (p1[1] - p0[1]).abs() < 1e-6
10078 {
10079 continue; }
10081 let (y0, y1) = (p0[1], p1[1]);
10082 if (y0 <= y && y1 > y) || (y1 <= y && y0 > y) {
10083 let t = (y - y0) / (y1 - y0);
10084 xs.push(p0[0] + t * (p1[0] - p0[0]));
10085 }
10086 }
10087 }
10088 xs.sort_by(|a, b| {
10089 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
10090 });
10091 let mut k = 0;
10092 while k + 1 < xs.len() {
10093 let a = map([xs[k], y], pen);
10094 let b = map([xs[k + 1], y], pen);
10095 lines.push([a[0], a[1], a[2], b[0], b[1], b[2]]);
10096 k += 2;
10097 }
10098 }
10099 }
10100 }
10101 pen += go.advance;
10102 }
10103 let mut gfx = self.gfx.borrow_mut();
10104 let color = gfx.color;
10105 let near = -gfx.camera.zdist + 0.05;
10106 for l in &lines {
10107 let (mut ax, mut ay, mut az) = (l[0], l[1], l[2]);
10108 let (mut bx, mut by, mut bz) = (l[3], l[4], l[5]);
10109 let da = gfx.camera.depth(ax, ay, az);
10110 let db = gfx.camera.depth(bx, by, bz);
10111 if da <= near && db <= near {
10112 continue;
10113 }
10114 if da <= near {
10115 let t = (near - da) / (db - da);
10116 ax += t * (bx - ax);
10117 ay += t * (by - ay);
10118 az += t * (bz - az);
10119 } else if db <= near {
10120 let t = (near - da) / (db - da);
10121 bx = ax + t * (bx - ax);
10122 by = ay + t * (by - ay);
10123 bz = az + t * (bz - az);
10124 }
10125 let (sax, say, da2) = gfx.camera.project(ax, ay, az);
10126 let (sbx, sby, db2) = gfx.camera.project(bx, by, bz);
10127 let depth = (da2 + db2) / 2.0;
10128 gfx.depth_queue.push_line(depth, color, sax, say, sbx, sby);
10129 }
10130 }
10131 return Ok(Value::Unit);
10132 },
10133 #[cfg(target_arch = "wasm32")]
10134 "font_text_3d" | "ข้อความฟอนต์3มิติ" | "字体3D" | "フォント3D" | "글꼴3D" | "متن_فونت_سهبعدی" | "نص_خط_ثلاثي_الأبعاد" | "טקסט_גופן_תלת_ממדי" | "تھری_ڈی_فونٹ_متن" | "texte_police_3d" | "schriftart_text_3d" | "текст_шрифт_3d" =>
10135 {
10136 return Ok(Value::Unit);
10137 },
10138 #[cfg(not(target_arch = "wasm32"))]
10140 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" | "عرض_فونت" | "عرض_الخط" | "רוחב_גופן" | "فونٹ_چوڑائی" | "largeur_police" | "schriftart_breite" | "ширина_шрифта" =>
10141 {
10142 let id = self.arg_num(&args, 0, 0.0)? as i64;
10143 let px = self.arg_num(&args, 1, 16.0)? as f32;
10144 let s = self.arg_str(&args, 2, "");
10145 if id >= 0 && (id as usize) < self.fonts.len() {
10146 return Ok(Value::Number(self.fonts[id as usize].measure(&s, px) as f64));
10147 }
10148 return Ok(Value::Number(0.0));
10149 },
10150 #[cfg(target_arch = "wasm32")]
10151 "font_width" | "ความกว้างฟอนต์" | "字体宽度" | "フォント幅" | "글꼴너비" | "عرض_فونت" | "عرض_الخط" | "רוחב_גופן" | "فونٹ_چوڑائی" | "largeur_police" | "schriftart_breite" | "ширина_шрифта" =>
10152 {
10153 return Ok(Value::Number(0.0));
10154 },
10155 #[cfg(not(target_arch = "wasm32"))]
10160 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10161 | "フォント輪郭" | "글꼴윤곽" | "خطدور_نویسه_فونت" | "حدود_حرف_الخط" | "קו_מתאר_גליף" | "فونٹ_گلف_آؤٹ_لائن" => {
10162 let id = self.arg_num(&args, 0, 0.0)? as i64;
10163 let s = self.arg_str(&args, 1, "");
10164 let tol = self.arg_num(&args, 2, 0.01)? as f32;
10165 let ch = s.chars().next().unwrap_or(' ');
10166 if id >= 0 && (id as usize) < self.fonts.len() {
10167 let go = self.fonts[id as usize].glyph_outline(ch, tol.max(1e-4));
10168 let mut contours: Vec<Value> = Vec::with_capacity(go.polylines.len());
10169 for pl in &go.polylines {
10170 let mut flat: Vec<Value> = Vec::with_capacity(pl.len() * 2);
10171 for p in pl {
10172 flat.push(Value::Number(p[0] as f64));
10173 flat.push(Value::Number(p[1] as f64));
10174 }
10175 contours.push(Value::List(Rc::new(flat)));
10176 }
10177 return Ok(Value::List(Rc::new(contours)));
10178 }
10179 return Ok(Value::List(Rc::new(vec![])));
10180 },
10181 #[cfg(target_arch = "wasm32")]
10182 "font_glyph_outline" | "font_outline" | "เส้นขอบฟอนต์" | "字体轮廓"
10183 | "フォント輪郭" | "글꼴윤곽" | "خطدور_نویسه_فونت" | "حدود_حرف_الخط" | "קו_מתאר_גליף" | "فونٹ_گلف_آؤٹ_لائن" => {
10184 return Ok(Value::List(Rc::new(vec![])));
10185 },
10186 #[cfg(not(target_arch = "wasm32"))]
10189 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" | "پیشروی_فونت" | "تقدم_الخط" | "קידום_גופן" | "فونٹ_ایڈوانس" => {
10190 let id = self.arg_num(&args, 0, 0.0)? as i64;
10191 let s = self.arg_str(&args, 1, "");
10192 let ch = s.chars().next().unwrap_or(' ');
10193 if id >= 0 && (id as usize) < self.fonts.len() {
10194 return Ok(Value::Number(self.fonts[id as usize].advance(ch) as f64));
10195 }
10196 return Ok(Value::Number(0.0));
10197 },
10198 #[cfg(target_arch = "wasm32")]
10199 "font_advance" | "ระยะฟอนต์" | "字体步进" | "フォント送り" | "글꼴전진" | "پیشروی_فونت" | "تقدم_الخط" | "קידום_גופן" | "فونٹ_ایڈوانس" => {
10200 return Ok(Value::Number(0.0));
10201 },
10202
10203 "ui_frame" | "边框" | "フレーム枠" | "프레임틀" | "กรอบ" | "قاب_رابط" | "إطار_الواجهة" | "מסגרת_ממשק" | "یو_آئی_فریم" | "cadre_ui" | "ui_rahmen" | "ui_рамка" => {
10205 let x = self.arg_num(&args, 0, 0.0)? as f32;
10206 let y = self.arg_num(&args, 1, 0.0)? as f32;
10207 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10208 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10209 let l = self.arg_num(&args, 4, 14.0)? as f32;
10210 let segs = ling_ui::holo::corner_brackets(x, y, w0, h0, l);
10211 let mut gfx = self.gfx.borrow_mut();
10212 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10213 for sg in segs {
10214 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10215 }
10216 return Ok(Value::Unit);
10217 },
10218 "ui_bevel" | "斜角框" | "ベベル枠" | "베벨틀" | "กรอบเฉียง" | "لبه_شیبدار" | "حافة_مشطوفة" | "מסגרת_משופעת" | "یو_آئی_بیول" | "biseau_ui" | "ui_fase" | "ui_фаска" =>
10220 {
10221 let x = self.arg_num(&args, 0, 0.0)? as f32;
10222 let y = self.arg_num(&args, 1, 0.0)? as f32;
10223 let w0 = self.arg_num(&args, 2, 0.0)? as f32;
10224 let h0 = self.arg_num(&args, 3, 0.0)? as f32;
10225 let bv = self.arg_num(&args, 4, 10.0)? as f32;
10226 let segs = ling_ui::holo::beveled_rect(x, y, w0, h0, bv);
10227 let mut gfx = self.gfx.borrow_mut();
10228 let (w, h, color) = (gfx.width, gfx.height, gfx.color);
10229 for sg in segs {
10230 draw_line(&mut gfx.buffer, w, h, color, sg[0], sg[1], sg[2], sg[3]);
10231 }
10232 return Ok(Value::Unit);
10233 },
10234
10235 #[cfg(not(target_arch = "wasm32"))]
10241 "ui_theme" | "界面主题" | "UIテーマ" | "인터페이스테마" | "ธีมส่วนติดต่อ" | "پوسته_رابط" | "سمة_الواجهة" | "ערכת_נושא" | "یو_آئی_تھیم" | "thème_ui" | "ui_thema" | "ui_тема" =>
10242 {
10243 let cur = self.ui_theme;
10244 let primary = self.color_at(&args, 0, cur.primary);
10245 let accent = self.color_at(&args, 3, cur.accent);
10246 let track = self.color_at(&args, 6, cur.track);
10247 let warn = self.color_at(&args, 9, cur.warn);
10248 let text = self.color_at(&args, 12, cur.text);
10249 let bg = self.color_at(&args, 15, cur.bg);
10250 self.ui_theme = UiTheme { primary, accent, track, warn, text, bg };
10251 return Ok(Value::Unit);
10252 },
10253
10254 "ui_theme_colors" | "인터페이스테마색상" => {
10260 let th = self.ui_theme;
10261 let mut out = Vec::with_capacity(18);
10262 for c in [th.primary, th.accent, th.track, th.warn, th.text, th.bg] {
10263 out.push(Value::Number(((c >> 16) & 0xFF) as f64));
10264 out.push(Value::Number(((c >> 8) & 0xFF) as f64));
10265 out.push(Value::Number((c & 0xFF) as f64));
10266 }
10267 return Ok(Value::List(Rc::new(out)));
10268 },
10269
10270 #[cfg(not(target_arch = "wasm32"))]
10272 "ui_radar" | "雷达" | "レーダー" | "레이더" | "เรดาร์" | "رادار_رابط" | "رادار_الواجهة" | "מכ״ם_ממשק" | "یو_آئی_ریڈار" | "radar_ui" | "ui_радар" => {
10273 let cx = self.arg_num(&args, 0, 0.)? as f32;
10274 let cy = self.arg_num(&args, 1, 0.)? as f32;
10275 let r = self.arg_num(&args, 2, 60.)? as f32;
10276 let sweep = self.arg_num(&args, 3, 0.)? as f32;
10277 let th = self.ui_theme;
10278 let prim = self.color_at(&args, 4, th.primary);
10279 self.draw_ui(&ling_ui::widgets::radar(
10280 cx, cy, r, sweep, prim, th.accent, th.track,
10281 ));
10282 return Ok(Value::Unit);
10283 },
10284 #[cfg(not(target_arch = "wasm32"))]
10285 "ui_compass" | "罗盘" | "コンパス" | "나침반" | "เข็มทิศ" | "قطبنمای_رابط" | "بوصلة_الواجهة" | "מצפן_ממשק" | "یو_آئی_قطب_نما" | "boussole_ui" | "ui_kompass" | "ui_компас" => {
10286 let x = self.arg_num(&args, 0, 0.)? as f32;
10287 let y = self.arg_num(&args, 1, 0.)? as f32;
10288 let w0 = self.arg_num(&args, 2, 300.)? as f32;
10289 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10290 let head = self.arg_num(&args, 4, 0.)? as f32;
10291 let th = self.ui_theme;
10292 let prim = self.color_at(&args, 5, th.primary);
10293 self.draw_ui(&ling_ui::widgets::compass(
10294 x, y, w0, h0, head, prim, th.track,
10295 ));
10296 return Ok(Value::Unit);
10297 },
10298 #[cfg(not(target_arch = "wasm32"))]
10299 "ui_reticle" | "准星" | "照準" | "조준선" | "เป้าเล็ง" | "نشانه_رابط" | "علامة_تصويب" | "כוונת" | "نشانہ" | "réticule_ui" | "ui_fadenkreuz" | "ui_прицел" => {
10300 let cx = self.arg_num(&args, 0, 0.)? as f32;
10301 let cy = self.arg_num(&args, 1, 0.)? as f32;
10302 let r = self.arg_num(&args, 2, 30.)? as f32;
10303 let spread = self.arg_num(&args, 3, 0.)? as f32;
10304 let th = self.ui_theme;
10305 let prim = self.color_at(&args, 4, th.primary);
10306 self.draw_ui(&ling_ui::widgets::reticle(cx, cy, r, spread, prim));
10307 return Ok(Value::Unit);
10308 },
10309 #[cfg(not(target_arch = "wasm32"))]
10310 "ui_target" | "锁定框" | "ターゲット" | "표적" | "กรอบเป้า" | "قاب_هدف" | "إطار_الهدف" | "מסגרת_מטרה" | "ہدف_فریم" | "cible_ui" | "ui_ziel" | "ui_цель" =>
10311 {
10312 let x = self.arg_num(&args, 0, 0.)? as f32;
10313 let y = self.arg_num(&args, 1, 0.)? as f32;
10314 let w0 = self.arg_num(&args, 2, 80.)? as f32;
10315 let h0 = self.arg_num(&args, 3, 80.)? as f32;
10316 let lock = self.arg_num(&args, 4, 0.)? as f32;
10317 let th = self.ui_theme;
10318 let prim = self.color_at(&args, 5, th.primary);
10319 self.draw_ui(&ling_ui::widgets::target(
10320 x, y, w0, h0, lock, prim, th.accent,
10321 ));
10322 return Ok(Value::Unit);
10323 },
10324 #[cfg(not(target_arch = "wasm32"))]
10325 "ui_panel" | "面板" | "パネル" | "패널" | "แผง" | "پنل_رابط" | "لوحة_الواجهة" | "לוח_ממשק" | "یو_آئی_پینل" | "panneau_ui" | "ui_feld" | "ui_панель" => {
10326 let x = self.arg_num(&args, 0, 0.)? as f32;
10327 let y = self.arg_num(&args, 1, 0.)? as f32;
10328 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10329 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10330 let bv = self.arg_num(&args, 4, 12.)? as f32;
10331 let th = self.ui_theme;
10332 let prim = self.color_at(&args, 5, th.primary);
10333 self.draw_ui(&ling_ui::widgets::panel(x, y, w0, h0, bv, prim, th.bg));
10334 return Ok(Value::Unit);
10335 },
10336 #[cfg(not(target_arch = "wasm32"))]
10337 "ui_scanlines" | "扫描线" | "走査線" | "스캔라인" | "เส้นสแกน" | "خطوط_اسکن" | "خطوط_المسح" | "קווי_סריקה" | "اسکین_لائنز" | "lignes_balayage_ui" | "ui_abtastzeilen" | "ui_линии_развёртки" =>
10338 {
10339 let x = self.arg_num(&args, 0, 0.)? as f32;
10340 let y = self.arg_num(&args, 1, 0.)? as f32;
10341 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10342 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10343 let dens = self.arg_num(&args, 4, 24.)? as usize;
10344 let th = self.ui_theme;
10345 let line = self.color_at(&args, 5, th.track);
10346 self.draw_ui(&ling_ui::widgets::scanlines(x, y, w0, h0, dens, line));
10347 return Ok(Value::Unit);
10348 },
10349
10350 #[cfg(not(target_arch = "wasm32"))]
10352 "ui_bar" | "进度条" | "バー" | "막대" | "แถบ" | "نوار_رابط" | "شريط_الواجهة" | "סרגל_ממשק" | "یو_آئی_بار" | "barre_ui" | "ui_leiste" | "ui_полоса" => {
10353 let x = self.arg_num(&args, 0, 0.)? as f32;
10354 let y = self.arg_num(&args, 1, 0.)? as f32;
10355 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10356 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10357 let val = self.arg_num(&args, 4, 0.)? as f32;
10358 let max = self.arg_num(&args, 5, 1.)? as f32;
10359 let th = self.ui_theme;
10360 let fill = self.color_at(&args, 6, th.primary);
10361 self.draw_ui(&ling_ui::widgets::bar(
10362 x,
10363 y,
10364 w0,
10365 h0,
10366 val / max.max(1e-6),
10367 fill,
10368 th.track,
10369 ));
10370 return Ok(Value::Unit);
10371 },
10372 #[cfg(not(target_arch = "wasm32"))]
10373 "ui_segbar" | "分段条" | "分割バー" | "분할막대" | "แถบแบ่ง" | "نوار_قطعهای" | "شريط_مقسم" | "סרגל_מקוטע" | "سیگمنٹ_بار" | "barre_segmentée_ui" | "ui_segmentleiste" | "ui_сегментная_полоса" =>
10374 {
10375 let x = self.arg_num(&args, 0, 0.)? as f32;
10376 let y = self.arg_num(&args, 1, 0.)? as f32;
10377 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10378 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10379 let val = self.arg_num(&args, 4, 0.)? as f32;
10380 let max = self.arg_num(&args, 5, 1.)? as f32;
10381 let segs = self.arg_num(&args, 6, 10.)? as usize;
10382 let th = self.ui_theme;
10383 let fill = self.color_at(&args, 7, th.primary);
10384 self.draw_ui(&ling_ui::widgets::segbar(
10385 x,
10386 y,
10387 w0,
10388 h0,
10389 val / max.max(1e-6),
10390 segs,
10391 fill,
10392 th.track,
10393 ));
10394 return Ok(Value::Unit);
10395 },
10396 #[cfg(not(target_arch = "wasm32"))]
10397 "ui_gauge" | "仪表" | "ゲージ" | "게이지" | "มาตรวัด" | "گیج_رابط" | "مقياس_الواجهة" | "מד_ממשק" | "یو_آئی_گیج" | "jauge_ui" | "ui_anzeige" | "ui_индикатор" => {
10398 let cx = self.arg_num(&args, 0, 0.)? as f32;
10399 let cy = self.arg_num(&args, 1, 0.)? as f32;
10400 let r = self.arg_num(&args, 2, 50.)? as f32;
10401 let val = self.arg_num(&args, 3, 0.)? as f32;
10402 let max = self.arg_num(&args, 4, 1.)? as f32;
10403 let th = self.ui_theme;
10404 let needle = self.color_at(&args, 5, th.warn);
10405 self.draw_ui(&ling_ui::widgets::gauge(
10406 cx,
10407 cy,
10408 r,
10409 val / max.max(1e-6),
10410 needle,
10411 th.accent,
10412 th.track,
10413 ));
10414 return Ok(Value::Unit);
10415 },
10416 #[cfg(not(target_arch = "wasm32"))]
10417 "ui_ring" | "环表" | "リングメーター" | "링미터" | "วงแหวนวัด" | "حلقه_گیج" | "حلقة_قياس" | "טבעת_מד" | "رنگ_گیج" | "anneau_ui" | "ui_кольцо" =>
10418 {
10419 let cx = self.arg_num(&args, 0, 0.)? as f32;
10420 let cy = self.arg_num(&args, 1, 0.)? as f32;
10421 let r = self.arg_num(&args, 2, 40.)? as f32;
10422 let val = self.arg_num(&args, 3, 0.)? as f32;
10423 let max = self.arg_num(&args, 4, 1.)? as f32;
10424 let th = self.ui_theme;
10425 let fill = self.color_at(&args, 5, th.primary);
10426 self.draw_ui(&ling_ui::widgets::ring(
10427 cx,
10428 cy,
10429 r,
10430 val / max.max(1e-6),
10431 fill,
10432 th.track,
10433 ));
10434 return Ok(Value::Unit);
10435 },
10436 #[cfg(not(target_arch = "wasm32"))]
10437 "ui_vu" | "音量条" | "VUメーター" | "음량막대" | "มาตรเสียง" | "گیج_صدا" | "مقياس_مستوى_الصوت" | "מד_עוצמה" | "وی_یو_میٹر" | "vumètre_ui" | "ui_vumeter" | "ui_вю_метр" =>
10438 {
10439 let x = self.arg_num(&args, 0, 0.)? as f32;
10440 let y = self.arg_num(&args, 1, 0.)? as f32;
10441 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10442 let h0 = self.arg_num(&args, 3, 60.)? as f32;
10443 let levels = self.arg_list_f32(&args, 4);
10444 let th = self.ui_theme;
10445 let fill = self.color_at(&args, 5, th.primary);
10446 self.draw_ui(&ling_ui::widgets::vu(x, y, w0, h0, &levels, fill, th.warn));
10447 return Ok(Value::Unit);
10448 },
10449 #[cfg(not(target_arch = "wasm32"))]
10450 "ui_spark" | "迷你图" | "スパークライン" | "스파크라인" | "กราฟจิ๋ว" | "نمودار_ریز" | "رسم_مصغر" | "גרף_זעיר" | "اسپارک_لائن" | "mini_graphe_ui" | "ui_sparkline" | "ui_мини_график" =>
10451 {
10452 let x = self.arg_num(&args, 0, 0.)? as f32;
10453 let y = self.arg_num(&args, 1, 0.)? as f32;
10454 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10455 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10456 let vals = self.arg_list_f32(&args, 4);
10457 let th = self.ui_theme;
10458 let line = self.color_at(&args, 5, th.accent);
10459 self.draw_ui(&ling_ui::widgets::spark(x, y, w0, h0, &vals, line));
10460 return Ok(Value::Unit);
10461 },
10462 #[cfg(not(target_arch = "wasm32"))]
10463 "ui_battery" | "电池" | "バッテリー" | "배터리" | "แบตเตอรี่" | "نشانگر_باتری" | "مؤشر_البطارية" | "מחוון_סוללה" | "بیٹری_انڈیکیٹر" | "batterie_ui" | "ui_batterie" | "ui_батарея" =>
10464 {
10465 let x = self.arg_num(&args, 0, 0.)? as f32;
10466 let y = self.arg_num(&args, 1, 0.)? as f32;
10467 let w0 = self.arg_num(&args, 2, 50.)? as f32;
10468 let h0 = self.arg_num(&args, 3, 22.)? as f32;
10469 let val = self.arg_num(&args, 4, 1.)? as f32;
10470 let max = self.arg_num(&args, 5, 1.)? as f32;
10471 let th = self.ui_theme;
10472 let fill = self.color_at(&args, 6, th.accent);
10473 self.draw_ui(&ling_ui::widgets::battery(
10474 x,
10475 y,
10476 w0,
10477 h0,
10478 val / max.max(1e-6),
10479 fill,
10480 th.track,
10481 th.warn,
10482 ));
10483 return Ok(Value::Unit);
10484 },
10485
10486 #[cfg(not(target_arch = "wasm32"))]
10488 "ui_button" | "按钮" | "ボタン" | "버튼" | "ปุ่ม" | "دکمه_رابط" | "زر_الواجهة" | "כפתור_ממשק" | "یو_آئی_بٹن" | "bouton_ui" | "ui_knopf" | "ui_кнопка" => {
10489 let x = self.arg_num(&args, 0, 0.)? as f32;
10490 let y = self.arg_num(&args, 1, 0.)? as f32;
10491 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10492 let h0 = self.arg_num(&args, 3, 40.)? as f32;
10493 let (mx, my, down) = self.mouse_now();
10494 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10495 let clicked = hover && down && !self.mouse_was_down;
10496 let th = self.ui_theme;
10497 let prim = self.color_at(&args, 4, th.primary);
10498 self.draw_ui(&ling_ui::widgets::button(
10499 x,
10500 y,
10501 w0,
10502 h0,
10503 hover,
10504 down && hover,
10505 prim,
10506 th.bg,
10507 ));
10508 return Ok(Value::Number(if clicked { 1.0 } else { 0.0 }));
10509 },
10510 #[cfg(not(target_arch = "wasm32"))]
10511 "ui_toggle" | "开关" | "トグル" | "토글" | "สวิตช์" | "کلید_ضامن" | "مفتاح_تبديل" | "מתג" | "ٹوگل" | "bascule_ui" | "ui_schalter" | "ui_переключатель" => {
10512 let x = self.arg_num(&args, 0, 0.)? as f32;
10513 let y = self.arg_num(&args, 1, 0.)? as f32;
10514 let w0 = self.arg_num(&args, 2, 52.)? as f32;
10515 let h0 = self.arg_num(&args, 3, 24.)? as f32;
10516 let mut state = self.arg_num(&args, 4, 0.)? > 0.5;
10517 let (mx, my, down) = self.mouse_now();
10518 let hover = ling_ui::holo::hit_rect(mx, my, x, y, w0, h0);
10519 if hover && down && !self.mouse_was_down {
10520 state = !state;
10521 }
10522 let th = self.ui_theme;
10523 let on = self.color_at(&args, 5, th.accent);
10524 self.draw_ui(&ling_ui::widgets::toggle(x, y, w0, h0, state, on, th.track));
10525 return Ok(Value::Number(if state { 1.0 } else { 0.0 }));
10526 },
10527 #[cfg(not(target_arch = "wasm32"))]
10528 "ui_slider" | "滑块" | "スライダー" | "슬라이더" | "แถบเลื่อน" | "لغزنده" | "شريط_انزلاق" | "מחוון_החלקה" | "سلائیڈر" | "curseur_ui" | "ui_schieberegler" | "ui_ползунок" =>
10529 {
10530 let x = self.arg_num(&args, 0, 0.)? as f32;
10531 let y = self.arg_num(&args, 1, 0.)? as f32;
10532 let w0 = self.arg_num(&args, 2, 160.)? as f32;
10533 let mut val = self.arg_num(&args, 3, 0.)? as f32;
10534 let mn = self.arg_num(&args, 4, 0.)? as f32;
10535 let mx_ = self.arg_num(&args, 5, 1.)? as f32;
10536 let (mx, my, down) = self.mouse_now();
10537 let hover = ling_ui::holo::hit_rect(mx, my, x - 8.0, y - 10.0, w0 + 16.0, 20.0);
10538 if hover && down {
10539 let frac = ((mx - x) / w0).clamp(0.0, 1.0);
10540 val = mn + (mx_ - mn) * frac;
10541 }
10542 let frac = ((val - mn) / (mx_ - mn).abs().max(1e-6)).clamp(0.0, 1.0);
10543 let th = self.ui_theme;
10544 let fill = self.color_at(&args, 6, th.primary);
10545 self.draw_ui(&ling_ui::widgets::slider(
10546 x, y, w0, frac, hover, fill, th.track,
10547 ));
10548 return Ok(Value::Number(val as f64));
10549 },
10550 #[cfg(not(target_arch = "wasm32"))]
10551 "ui_checkbox" | "复选框" | "チェックボックス" | "체크박스" | "ช่องเลือก" | "جعبه_علامت" | "مربع_اختيار" | "תיבת_סימון" | "چیک_باکس" | "case_cocher_ui" | "ui_kontrollkästchen" | "ui_флажок" =>
10552 {
10553 let x = self.arg_num(&args, 0, 0.)? as f32;
10554 let y = self.arg_num(&args, 1, 0.)? as f32;
10555 let s = self.arg_num(&args, 2, 20.)? as f32;
10556 let mut checked = self.arg_num(&args, 3, 0.)? > 0.5;
10557 let (mx, my, down) = self.mouse_now();
10558 let hover = ling_ui::holo::hit_rect(mx, my, x, y, s, s);
10559 if hover && down && !self.mouse_was_down {
10560 checked = !checked;
10561 }
10562 let th = self.ui_theme;
10563 let prim = self.color_at(&args, 4, th.primary);
10564 self.draw_ui(&ling_ui::widgets::checkbox(
10565 x, y, s, checked, hover, prim, th.track,
10566 ));
10567 return Ok(Value::Number(if checked { 1.0 } else { 0.0 }));
10568 },
10569 #[cfg(not(target_arch = "wasm32"))]
10570 "ui_tabs" | "标签页" | "タブ" | "탭" | "แท็บ" | "برگهها" | "ألسنة_الواجهة" | "לשוניות" | "ٹیبز" | "onglets_ui" | "ui_reiter" | "ui_вкладки" => {
10571 let x = self.arg_num(&args, 0, 0.)? as f32;
10572 let y = self.arg_num(&args, 1, 0.)? as f32;
10573 let w0 = self.arg_num(&args, 2, 240.)? as f32;
10574 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10575 let count = self.arg_num(&args, 4, 3.)? as usize;
10576 let mut active = self.arg_num(&args, 5, 0.)? as i32;
10577 let (mx, my, down) = self.mouse_now();
10578 let mut hover = -1;
10579 if my >= y && my <= y + h0 && mx >= x && mx <= x + w0 && count > 0 {
10580 hover = (((mx - x) / (w0 / count as f32)) as i32)
10581 .max(0)
10582 .min(count as i32 - 1);
10583 if down && !self.mouse_was_down {
10584 active = hover;
10585 }
10586 }
10587 let th = self.ui_theme;
10588 let prim = self.color_at(&args, 6, th.primary);
10589 self.draw_ui(&ling_ui::widgets::tabs(
10590 x,
10591 y,
10592 w0,
10593 h0,
10594 count,
10595 active as usize,
10596 hover,
10597 prim,
10598 th.track,
10599 ));
10600 return Ok(Value::Number(active as f64));
10601 },
10602 #[cfg(not(target_arch = "wasm32"))]
10603 "ui_progress" | "进度" | "プログレス" | "진행바" | "ความคืบหน้า" | "نوار_پیشرفت" | "شريط_التقدم" | "פס_התקדמות" | "پیش_رفت_بار" | "progression_ui" | "ui_fortschritt" | "ui_прогресс" =>
10604 {
10605 let x = self.arg_num(&args, 0, 0.)? as f32;
10606 let y = self.arg_num(&args, 1, 0.)? as f32;
10607 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10608 let h0 = self.arg_num(&args, 3, 12.)? as f32;
10609 let frac = self.arg_num(&args, 4, 0.)? as f32;
10610 let th = self.ui_theme;
10611 let fill = self.color_at(&args, 5, th.accent);
10612 self.draw_ui(&ling_ui::widgets::progress(
10613 x, y, w0, h0, frac, fill, th.track,
10614 ));
10615 return Ok(Value::Unit);
10616 },
10617 #[cfg(not(target_arch = "wasm32"))]
10618 "ui_tooltip" | "提示框" | "ツールチップ" | "툴팁" | "คำแนะนำ" | "راهنمای_شناور" | "تلميح_الواجهة" | "חלונית_עזרה" | "ٹول_ٹپ" | "infobulle_ui" | "ui_подсказка" =>
10619 {
10620 let x = self.arg_num(&args, 0, 0.)? as f32;
10621 let y = self.arg_num(&args, 1, 0.)? as f32;
10622 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10623 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10624 let th = self.ui_theme;
10625 let prim = self.color_at(&args, 4, th.primary);
10626 self.draw_ui(&ling_ui::widgets::tooltip(x, y, w0, h0, prim, th.bg));
10627 return Ok(Value::Unit);
10628 },
10629 #[cfg(not(target_arch = "wasm32"))]
10630 "ui_stepper" | "步进器" | "ステッパー" | "스테퍼" | "ตัวปรับค่า" | "پلهگر" | "زر_خطوات" | "בורר_מדורג" | "اسٹیپر" | "pas_à_pas_ui" | "ui_schrittsteuerung" | "ui_степпер" =>
10631 {
10632 let x = self.arg_num(&args, 0, 0.)? as f32;
10633 let y = self.arg_num(&args, 1, 0.)? as f32;
10634 let w0 = self.arg_num(&args, 2, 120.)? as f32;
10635 let h0 = self.arg_num(&args, 3, 28.)? as f32;
10636 let mut val = self.arg_num(&args, 4, 0.)? as f32;
10637 let step = self.arg_num(&args, 5, 1.)? as f32;
10638 let (mx, my, down) = self.mouse_now();
10639 let hm = ling_ui::holo::hit_rect(mx, my, x, y, h0, h0);
10640 let hp = ling_ui::holo::hit_rect(mx, my, x + w0 - h0, y, h0, h0);
10641 if down && !self.mouse_was_down {
10642 if hm {
10643 val -= step;
10644 }
10645 if hp {
10646 val += step;
10647 }
10648 }
10649 let th = self.ui_theme;
10650 let prim = self.color_at(&args, 6, th.primary);
10651 self.draw_ui(&ling_ui::widgets::stepper(
10652 x, y, w0, h0, hm, hp, prim, th.track,
10653 ));
10654 return Ok(Value::Number(val as f64));
10655 },
10656
10657 #[cfg(not(target_arch = "wasm32"))]
10659 "ui_healthbar" | "血条" | "体力バー" | "체력바" | "แถบพลังชีวิต" | "نوار_سلامتی" | "شريط_الصحة" | "פס_בריאות" | "ہیلتھ_بار" | "barre_vie_ui" | "ui_lebensbalken" | "ui_полоса_здоровья" =>
10660 {
10661 let x = self.arg_num(&args, 0, 0.)? as f32;
10662 let y = self.arg_num(&args, 1, 0.)? as f32;
10663 let w0 = self.arg_num(&args, 2, 180.)? as f32;
10664 let h0 = self.arg_num(&args, 3, 16.)? as f32;
10665 let val = self.arg_num(&args, 4, 1.)? as f32;
10666 let max = self.arg_num(&args, 5, 1.)? as f32;
10667 let pulse = self.arg_num(&args, 6, 0.)? as f32;
10668 let th = self.ui_theme;
10669 let full = self.color_at(&args, 7, th.accent);
10670 self.draw_ui(&ling_ui::widgets::healthbar(
10671 x,
10672 y,
10673 w0,
10674 h0,
10675 val / max.max(1e-6),
10676 pulse,
10677 full,
10678 th.warn,
10679 th.track,
10680 ));
10681 return Ok(Value::Unit);
10682 },
10683 #[cfg(not(target_arch = "wasm32"))]
10684 "ui_cooldown" | "冷却" | "クールダウン" | "쿨다운" | "คูลดาวน์" | "زمان_خنکسازی" | "مؤقت_التهدئة" | "זמן_קירור" | "کول_ڈاؤن" | "recharge_ui" | "ui_abklingzeit" | "ui_перезарядка" =>
10685 {
10686 let cx = self.arg_num(&args, 0, 0.)? as f32;
10687 let cy = self.arg_num(&args, 1, 0.)? as f32;
10688 let r = self.arg_num(&args, 2, 28.)? as f32;
10689 let frac = self.arg_num(&args, 3, 0.)? as f32;
10690 let th = self.ui_theme;
10691 let fill = self.color_at(&args, 4, th.primary);
10692 self.draw_ui(&ling_ui::widgets::cooldown(cx, cy, r, frac, fill, th.track));
10693 return Ok(Value::Unit);
10694 },
10695 #[cfg(not(target_arch = "wasm32"))]
10696 "ui_counter" | "计数器" | "カウンター" | "카운터" | "ตัวนับ" | "شمارشگر" | "عداد_الواجهة" | "מונה_ממשק" | "کاؤنٹر" | "compteur_ui" | "ui_zähler" | "ui_счётчик" => {
10697 let x = self.arg_num(&args, 0, 0.)? as f32;
10698 let y = self.arg_num(&args, 1, 0.)? as f32;
10699 let dw = self.arg_num(&args, 2, 14.)? as f32;
10700 let dh = self.arg_num(&args, 3, 24.)? as f32;
10701 let val = self.arg_num(&args, 4, 0.)? as i64;
10702 let digits = self.arg_num(&args, 5, 4.)? as usize;
10703 let th = self.ui_theme;
10704 let on = self.color_at(&args, 6, th.primary);
10705 let off = ling_ui::widgets::shade(th.track, 0.5);
10706 self.draw_ui(&ling_ui::widgets::counter(
10707 x, y, dw, dh, val, digits, on, off,
10708 ));
10709 return Ok(Value::Unit);
10710 },
10711 #[cfg(not(target_arch = "wasm32"))]
10712 "ui_minimap" | "小地图" | "ミニマップ" | "미니맵" | "แผนที่ย่อ" | "نقشه_کوچک" | "خريطة_مصغرة" | "מפה_מוקטנת" | "منی_میپ" | "minicarte_ui" | "ui_minikarte" | "ui_миникарта" =>
10713 {
10714 let x = self.arg_num(&args, 0, 0.)? as f32;
10715 let y = self.arg_num(&args, 1, 0.)? as f32;
10716 let w0 = self.arg_num(&args, 2, 140.)? as f32;
10717 let h0 = self.arg_num(&args, 3, 140.)? as f32;
10718 let th = self.ui_theme;
10719 let prim = self.color_at(&args, 4, th.primary);
10720 self.draw_ui(&ling_ui::widgets::minimap(x, y, w0, h0, prim, th.bg));
10721 return Ok(Value::Unit);
10722 },
10723 #[cfg(not(target_arch = "wasm32"))]
10724 "ui_dpad" | "方向键" | "方向パッド" | "방향패드" | "ปุ่มทิศทาง" | "دسته_جهتدار" | "لوحة_الاتجاهات" | "לוח_כיוונים" | "ڈی_پیڈ" | "croix_direction_ui" | "ui_steuerkreuz" | "ui_крестовина" =>
10725 {
10726 let cx = self.arg_num(&args, 0, 0.)? as f32;
10727 let cy = self.arg_num(&args, 1, 0.)? as f32;
10728 let r = self.arg_num(&args, 2, 50.)? as f32;
10729 let (mx, my, down) = self.mouse_now();
10730 let mut dir = 0;
10731 if down {
10732 let (dx, dy) = (mx - cx, my - cy);
10733 if dx * dx + dy * dy <= r * r {
10734 if dx.abs() > dy.abs() {
10735 dir = if dx > 0.0 { 2 } else { 4 };
10736 } else {
10737 dir = if dy > 0.0 { 3 } else { 1 };
10738 }
10739 }
10740 }
10741 let th = self.ui_theme;
10742 let prim = self.color_at(&args, 3, th.primary);
10743 self.draw_ui(&ling_ui::widgets::dpad(cx, cy, r, dir, prim, th.track));
10744 return Ok(Value::Number(dir as f64));
10745 },
10746 #[cfg(not(target_arch = "wasm32"))]
10747 "ui_slotgrid" | "物品格" | "スロットグリッド" | "슬롯격자" | "ช่องไอเทม" | "شبکه_شیار" | "شبكة_الفتحات" | "רשת_חריצים" | "سلاٹ_گرڈ" | "grille_emplacements_ui" | "ui_slotraster" | "ui_сетка_слотов" =>
10748 {
10749 let x = self.arg_num(&args, 0, 0.)? as f32;
10750 let y = self.arg_num(&args, 1, 0.)? as f32;
10751 let cols = self.arg_num(&args, 2, 4.)? as usize;
10752 let rows = self.arg_num(&args, 3, 1.)? as usize;
10753 let cell = self.arg_num(&args, 4, 36.)? as f32;
10754 let sel = self.arg_num(&args, 5, -1.)? as i32;
10755 let th = self.ui_theme;
10756 let prim = self.color_at(&args, 6, th.primary);
10757 self.draw_ui(&ling_ui::widgets::slotgrid(
10758 x, y, cols, rows, cell, sel, prim, th.track,
10759 ));
10760 return Ok(Value::Unit);
10761 },
10762 #[cfg(not(target_arch = "wasm32"))]
10763 "ui_vignette" | "暗角" | "ビネット" | "비네트" | "ขอบมืด" | "سایهگرد_کادر" | "تظليل_الحواف" | "הצללת_מסגרת" | "ویگنیٹ" | "vignette_ui" | "ui_виньетка" => {
10764 let intensity = self.arg_num(&args, 0, 0.5)? as f32;
10765 let (w, h) = {
10766 let g = self.gfx.borrow();
10767 (g.width as f32, g.height as f32)
10768 };
10769 let th = self.ui_theme;
10770 let col = self.color_at(&args, 1, th.warn);
10771 self.draw_ui(&ling_ui::widgets::vignette(w, h, intensity, col));
10772 return Ok(Value::Unit);
10773 },
10774
10775 #[cfg(not(target_arch = "wasm32"))]
10777 "ui_gauge3d" | "立体仪表" | "立体ゲージ" | "입체게이지" | "มาตรวัด3มิติ" | "گیج_سهبعدی" | "مقياس_ثلاثي_الأبعاد" | "מד_תלת_ממדי" | "تھری_ڈی_گیج" | "jauge_3d_ui" | "ui_anzeige_3d" | "ui_индикатор_3d" =>
10778 {
10779 let cx = self.arg_num(&args, 0, 0.)? as f32;
10780 let cy = self.arg_num(&args, 1, 0.)? as f32;
10781 let r = self.arg_num(&args, 2, 50.)? as f32;
10782 let val = self.arg_num(&args, 3, 0.)? as f32;
10783 let max = self.arg_num(&args, 4, 1.)? as f32;
10784 let spin = self.arg_num(&args, 5, 0.)? as f32;
10785 let th = self.ui_theme;
10786 let fill = self.color_at(&args, 6, th.primary);
10787 self.draw_ui(&ling_ui::widgets::gauge3d(
10788 cx,
10789 cy,
10790 r,
10791 val / max.max(1e-6),
10792 spin,
10793 fill,
10794 th.track,
10795 ));
10796 return Ok(Value::Unit);
10797 },
10798 #[cfg(not(target_arch = "wasm32"))]
10799 "ui_panel3d" | "立体面板" | "立体パネル" | "입체패널" | "แผง3มิติ" | "پنل_سهبعدی" | "لوحة_ثلاثية_الأبعاد" | "לוח_תלת_ממדי" | "تھری_ڈی_پینل" | "panneau_3d_ui" | "ui_feld_3d" | "ui_панель_3d" =>
10800 {
10801 let x = self.arg_num(&args, 0, 0.)? as f32;
10802 let y = self.arg_num(&args, 1, 0.)? as f32;
10803 let w0 = self.arg_num(&args, 2, 200.)? as f32;
10804 let h0 = self.arg_num(&args, 3, 120.)? as f32;
10805 let depth = self.arg_num(&args, 4, 14.)? as f32;
10806 let th = self.ui_theme;
10807 let prim = self.color_at(&args, 5, th.primary);
10808 self.draw_ui(&ling_ui::widgets::panel3d(x, y, w0, h0, depth, prim, th.bg));
10809 return Ok(Value::Unit);
10810 },
10811 #[cfg(not(target_arch = "wasm32"))]
10812 "ui_radar3d" | "立体雷达" | "立体レーダー" | "입체레이더" | "เรดาร์3มิติ" | "رادار_سهبعدی" | "رادار_ثلاثي_الأبعاد" | "מכ״ם_תלת_ממדי" | "تھری_ڈی_ریڈار" | "radar_3d_ui" | "ui_radar_3d" | "ui_радар_3d" =>
10813 {
10814 let cx = self.arg_num(&args, 0, 0.)? as f32;
10815 let cy = self.arg_num(&args, 1, 0.)? as f32;
10816 let r = self.arg_num(&args, 2, 60.)? as f32;
10817 let tilt = self.arg_num(&args, 3, 0.9)? as f32;
10818 let sweep = self.arg_num(&args, 4, 0.)? as f32;
10819 let th = self.ui_theme;
10820 let prim = self.color_at(&args, 5, th.primary);
10821 self.draw_ui(&ling_ui::widgets::radar3d(
10822 cx, cy, r, tilt, sweep, prim, th.track,
10823 ));
10824 return Ok(Value::Unit);
10825 },
10826
10827 #[cfg(not(target_arch = "wasm32"))]
10829 "audio_blip" | "提示音" | "ビープ音" | "효과음" | "เสียงบี๊บ" | "بوق_کوتاه" | "نغمة_قصيرة" | "ביפ" | "بلپ_آواز" | "bip_audio" | "звук_бип" =>
10830 {
10831 let freq = self.arg_num(&args, 0, 660.)? as f32;
10832 let dur = self.arg_num(&args, 1, 0.08)? as f32;
10833 let wave = Wave::from_name(&self.arg_str(&args, 2, "sine"));
10834 let amp = self.arg_num(&args, 3, 0.25)? as f32;
10835 if let Some(audio) = &self.audio {
10836 audio.blip(freq, amp, dur, wave);
10837 }
10838 return Ok(Value::Unit);
10839 },
10840 #[cfg(not(target_arch = "wasm32"))]
10841 "ui_sound" | "界面音" | "UI音" | "인터페이스음" | "เสียงปุ่ม" | "صدای_رابط" | "صوت_الواجهة" | "צליל_ממשק" | "یو_آئی_آواز" | "son_ui" | "ui_klang" | "ui_звук" =>
10842 {
10843 let name = self.arg_str(&args, 0, "click");
10844 if let Some(audio) = &self.audio {
10845 match name.as_str() {
10846 "hover" => audio.blip(880.0, 0.10, 0.04, Wave::Sine),
10847 "confirm" => {
10848 audio.blip(660.0, 0.22, 0.07, Wave::Square);
10849 audio.blip(990.0, 0.18, 0.10, Wave::Square);
10850 },
10851 "error" => {
10852 audio.blip(180.0, 0.30, 0.16, Wave::Saw);
10853 audio.blip(140.0, 0.30, 0.18, Wave::Saw);
10854 },
10855 "toggle" => audio.blip(520.0, 0.22, 0.05, Wave::Triangle),
10856 "tick" => audio.blip(1500.0, 0.12, 0.02, Wave::Square),
10857 _ => audio.blip(720.0, 0.26, 0.05, Wave::Square), }
10859 }
10860 return Ok(Value::Unit);
10861 },
10862
10863 #[cfg(not(target_arch = "wasm32"))]
10871 "music_load" | "载入音乐" | "音楽読込" | "음악로드" | "โหลดเพลง" | "بارگذاری_موسیقی" | "تحميل_الموسيقى" | "טעינת_מוזיקה" | "موسیقی_لوڈ" | "charger_musique" | "musik_laden" | "загрузить_музыку" =>
10872 {
10873 let path = self.arg_str(&args, 0, "");
10874 let resolved = if std::path::Path::new(&path).exists() {
10875 path.clone()
10876 } else if let Some(d) = &self.source_dir {
10877 d.join(&path).to_string_lossy().into_owned()
10878 } else {
10879 path.clone()
10880 };
10881 match ling_music::load(&resolved) {
10882 Ok(t) => {
10883 let id = self.tracks.len();
10884 self.tracks.push(t);
10885 return Ok(Value::Number(id as f64));
10886 },
10887 Err(e) => {
10888 eprintln!("music_load failed ({path}): {e}");
10889 return Ok(Value::Number(-1.0));
10890 },
10891 }
10892 },
10893 #[cfg(not(target_arch = "wasm32"))]
10894 "music_duration" | "音乐时长" | "音楽長さ" | "음악길이" | "ความยาวเพลง" | "مدت_موسیقی" | "مدة_الموسيقى" | "משך_מוזיקה" | "موسیقی_دورانیہ" | "durée_musique" | "musik_dauer" | "длительность_музыки" =>
10895 {
10896 let id = self.arg_num(&args, 0, 0.0)? as i64;
10897 let d = self
10898 .tracks
10899 .get(id as usize)
10900 .map(|t| t.duration)
10901 .unwrap_or(0.0);
10902 return Ok(Value::Number(d as f64));
10903 },
10904 #[cfg(not(target_arch = "wasm32"))]
10905 "music_bpm" | "节拍速度" | "テンポ" | "템포" | "จังหวะต่อนาที" | "ضربان_در_دقیقه" | "نبضات_بالدقيقة" | "פעימות_לדקה" | "بی_پی_ایم" | "bpm_musique" | "musik_bpm" | "музыка_bpm" =>
10906 {
10907 let id = self.arg_num(&args, 0, 0.0)? as i64;
10908 let b = self
10909 .tracks
10910 .get(id as usize)
10911 .map(|t| ling_music::analysis::bpm(&t.mono, t.rate))
10912 .unwrap_or(0.0);
10913 return Ok(Value::Number(b as f64));
10914 },
10915 #[cfg(not(target_arch = "wasm32"))]
10916 "music_key" | "调性" | "調性" | "조성" | "คีย์เพลง" | "گام_موسیقی" | "مقام_الموسيقى" | "סולם_מוזיקלי" | "موسیقی_کلید" | "tonalité_musique" | "musik_tonart" | "тональность_музыки" => {
10917 let id = self.arg_num(&args, 0, 0.0)? as i64;
10918 let k = self
10919 .tracks
10920 .get(id as usize)
10921 .map(|t| ling_music::analysis::key_name(&t.mono, t.rate))
10922 .unwrap_or_default();
10923 return Ok(Value::Str(k));
10924 },
10925 #[cfg(not(target_arch = "wasm32"))]
10926 "music_onsets" | "音符起点" | "オンセット" | "온셋" | "จุดเริ่มเสียง" | "آغازهای_نت" | "بدايات_النغمات" | "התחלות_תווים" | "نوٹ_شروعات" | "attaques_musique" | "musik_einsätze" | "атаки_музыки" =>
10927 {
10928 let id = self.arg_num(&args, 0, 0.0)? as i64;
10929 let v = self
10930 .tracks
10931 .get(id as usize)
10932 .map(|t| ling_music::analysis::onsets(&t.mono, t.rate))
10933 .unwrap_or_default();
10934 return Ok(Value::List(Rc::new(
10935 v.into_iter().map(|x| Value::Number(x as f64)).collect(),
10936 )));
10937 },
10938 #[cfg(not(target_arch = "wasm32"))]
10939 "music_beat_grid" | "节拍网格" | "ビートグリッド" | "비트그리드" | "กริดจังหวะ" | "شبکه_ضرب" | "شبكة_الإيقاع" | "רשת_פעימות" | "بیٹ_گرڈ" | "grille_temps_musique" | "musik_taktraster" | "сетка_ритма_музыки" =>
10940 {
10941 let id = self.arg_num(&args, 0, 0.0)? as i64;
10942 let beats = self
10943 .tracks
10944 .get(id as usize)
10945 .map(|t| {
10946 let b = ling_music::analysis::bpm(&t.mono, t.rate);
10947 ling_music::analysis::beat_grid(&t.mono, t.rate, b)
10948 })
10949 .unwrap_or_default();
10950 return Ok(Value::List(Rc::new(
10951 beats.into_iter().map(|x| Value::Number(x as f64)).collect(),
10952 )));
10953 },
10954
10955 #[cfg(not(target_arch = "wasm32"))]
10957 "music_play" | "播放音乐" | "音楽再生" | "음악재생" | "เล่นเพลง" | "پخش_موسیقی" | "شغّل_الموسيقى" | "נגן_מוזיקה" | "موسیقی_چلاؤ" | "jouer_musique" | "musik_abspielen" | "играть_музыку" =>
10958 {
10959 let id = self.arg_num(&args, 0, 0.0)? as i64;
10960 if self.ensure_music() {
10961 let track = self
10962 .tracks
10963 .get(id as usize)
10964 .map(|t| (t.stereo.clone(), t.rate));
10965 if let (Some((st, rate)), Some(m)) = (track, &self.music) {
10966 m.set_track(st, rate);
10967 m.play();
10968 } else if let Some(m) = &self.music {
10969 m.play();
10970 }
10971 }
10972 return Ok(Value::Unit);
10973 },
10974 #[cfg(not(target_arch = "wasm32"))]
10975 "music_pause" | "暂停音乐" | "音楽一時停止" | "음악일시정지" | "หยุดเพลงชั่วคราว" | "مکث_موسیقی" | "ألبث_الموسيقى" | "השהה_מוזיקה" | "موسیقی_روکو_مؤقت" | "pause_musique" | "musik_pausieren" | "пауза_музыки" =>
10976 {
10977 if let Some(m) = &self.music {
10978 m.pause();
10979 }
10980 return Ok(Value::Unit);
10981 },
10982 #[cfg(not(target_arch = "wasm32"))]
10983 "music_stop" | "停止音乐" | "音楽停止" | "음악정지" | "หยุดเพลง" | "توقف_موسیقی" | "أوقف_الموسيقى" | "עצור_מוזיקה" | "موسیقی_روکو" | "arrêter_musique" | "musik_stoppen" | "остановить_музыку" =>
10984 {
10985 if let Some(m) = &self.music {
10986 m.stop();
10987 }
10988 return Ok(Value::Unit);
10989 },
10990 #[cfg(not(target_arch = "wasm32"))]
10991 "music_seek" | "定位音乐" | "音楽シーク" | "음악탐색" | "ค้นหาเพลง" | "جستجوی_موسیقی" | "ابحث_في_الموسيقى" | "חפש_במוזיקה" | "موسیقی_تلاش" | "chercher_musique" | "musik_suchen" | "перемотать_музыку" =>
10992 {
10993 let sec = self.arg_num(&args, 0, 0.0)? as f32;
10994 if let Some(m) = &self.music {
10995 m.seek(sec);
10996 }
10997 return Ok(Value::Unit);
10998 },
10999 #[cfg(not(target_arch = "wasm32"))]
11000 "music_pos" | "音乐位置" | "音楽位置" | "음악위치" | "ตำแหน่งเพลง" | "موقعیت_موسیقی" | "موضع_الموسيقى" | "מיקום_מוזיקה" | "موسیقی_مقام" | "position_musique" | "musik_position" | "позиция_музыки" =>
11001 {
11002 let p = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
11003 return Ok(Value::Number(p as f64));
11004 },
11005 #[cfg(not(target_arch = "wasm32"))]
11006 "music_volume" | "音乐音量" | "音楽音量" | "음악음량" | "ระดับเพลง" | "بلندی_موسیقی" | "مستوى_الموسيقى" | "עוצמת_מוזיקה" | "موسیقی_شدت" | "volume_musique" | "musik_lautstärke" | "громкость_музыки" =>
11007 {
11008 let v = self.arg_num(&args, 0, 0.8)? as f32;
11009 if self.ensure_music() {
11010 if let Some(m) = &self.music {
11011 m.set_volume(v);
11012 }
11013 }
11014 return Ok(Value::Unit);
11015 },
11016
11017 #[cfg(not(target_arch = "wasm32"))]
11019 "music_patch" | "乐器音色" | "音色読込" | "악기패치" | "แพตช์เครื่องดนตรี" | "پچ_موسیقی" | "آلة_الموسيقى" | "תיקון_כלי_נגינה" | "میوزک_پیچ" | "patch_musique" | "musik_patch" | "патч_музыки" =>
11020 {
11021 let path = self.arg_str(&args, 0, "");
11022 let resolved = if std::path::Path::new(&path).exists() {
11023 path.clone()
11024 } else if let Some(d) = &self.source_dir {
11025 d.join(&path).to_string_lossy().into_owned()
11026 } else {
11027 path.clone()
11028 };
11029 if !self.ensure_music() {
11030 return Ok(Value::Number(-1.0));
11031 }
11032 match ling_music::patch::from_path(&resolved) {
11033 Ok(p) => {
11034 let id = self.music.as_ref().unwrap().add_patch(p);
11035 return Ok(Value::Number(id as f64));
11036 },
11037 Err(e) => {
11038 eprintln!("music_patch failed ({path}): {e}");
11039 return Ok(Value::Number(-1.0));
11040 },
11041 }
11042 },
11043 #[cfg(not(target_arch = "wasm32"))]
11044 "music_note" | "弹音符" | "音符演奏" | "음표연주" | "เล่นโน้ต" | "نواختن_نت" | "عزف_نغمة" | "נגן_תו" | "نوٹ_بجاؤ" | "note_musique" | "musik_note" | "нота_музыки" =>
11045 {
11046 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11047 let midi = self.pitch_arg(&args, 1, 60);
11048 let dur = self.arg_num(&args, 2, 0.5)? as f32;
11049 let vel = self.arg_num(&args, 3, 0.9)? as f32;
11050 if self.ensure_music() {
11051 if let Some(m) = &self.music {
11052 m.note(inst, midi, vel, dur);
11053 }
11054 }
11055 return Ok(Value::Unit);
11056 },
11057 #[cfg(not(target_arch = "wasm32"))]
11058 "music_note_on" | "音符开始" | "音符オン" | "음표켜기" | "โน้ตเริ่ม" | "شروع_نت" | "بدء_النغمة" | "התחלת_תו" | "نوٹ_شروع" | "note_musique_on" | "musik_note_an" | "нота_музыки_вкл" =>
11059 {
11060 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11061 let midi = self.pitch_arg(&args, 1, 60);
11062 let vel = self.arg_num(&args, 2, 0.9)? as f32;
11063 if self.ensure_music() {
11064 if let Some(m) = &self.music {
11065 m.note_on(inst, midi, vel);
11066 }
11067 }
11068 return Ok(Value::Unit);
11069 },
11070 #[cfg(not(target_arch = "wasm32"))]
11071 "music_note_off" | "音符结束" | "音符オフ" | "음표끄기" | "โน้ตจบ" | "پایان_نت" | "إيقاف_النغمة" | "סיום_תו" | "نوٹ_ختم" | "note_musique_off" | "musik_note_aus" | "нота_музыки_выкл" =>
11072 {
11073 let inst = self.arg_num(&args, 0, 0.0)? as usize;
11074 let midi = self.pitch_arg(&args, 1, 60);
11075 if let Some(m) = &self.music {
11076 m.note_off(inst, midi);
11077 }
11078 return Ok(Value::Unit);
11079 },
11080
11081 #[cfg(not(target_arch = "wasm32"))]
11083 "music_judge" | "判定" | "判定する" | "판정" | "ตัดสินจังหวะ" | "داوری_ضرب" | "حكم_الإيقاع" | "שיפוט_קצב" | "بیٹ_فیصلہ" | "juger_musique" | "musik_bewerten" | "оценить_музыку" =>
11084 {
11085 let delta_ms = self.arg_num(&args, 0, 9999.0)? as f32;
11086 return Ok(Value::Number(
11087 ling_music::Grade::judge(delta_ms).index() as f64
11088 ));
11089 },
11090 #[cfg(not(target_arch = "wasm32"))]
11091 "music_grade_name" | "判定名" | "判定名称" | "판정이름" | "ชื่อการตัดสิน" | "نام_رتبه" | "اسم_التقييم" | "שם_דירוג" | "گریڈ_نام" | "nom_grade_musique" | "musik_bewertungsname" | "имя_оценки_музыки" =>
11092 {
11093 let idx = self.arg_num(&args, 0, 4.0)? as i32;
11094 return Ok(Value::Str(
11095 ling_music::Grade::from_index(idx).name().to_string(),
11096 ));
11097 },
11098
11099 #[cfg(not(target_arch = "wasm32"))]
11101 "music_lrc" | "载入歌词" | "歌詞読込" | "가사로드" | "โหลดเนื้อเพลง" | "بارگذاری_متن_ترانه" | "تحميل_كلمات_الأغنية" | "טעינת_מילות_שיר" | "گیت_متن_لوڈ" | "lrc_musique" | "musik_lrc" | "lrc_музыки" =>
11102 {
11103 let path = self.arg_str(&args, 0, "");
11104 let resolved = if std::path::Path::new(&path).exists() {
11105 path.clone()
11106 } else if let Some(d) = &self.source_dir {
11107 d.join(&path).to_string_lossy().into_owned()
11108 } else {
11109 path.clone()
11110 };
11111 match std::fs::read_to_string(&resolved) {
11112 Ok(text) => {
11113 let id = self.lyrics.len();
11114 self.lyrics.push(ling_music::Lyrics::parse(&text));
11115 return Ok(Value::Number(id as f64));
11116 },
11117 Err(e) => {
11118 eprintln!("music_lrc failed ({path}): {e}");
11119 return Ok(Value::Number(-1.0));
11120 },
11121 }
11122 },
11123 #[cfg(not(target_arch = "wasm32"))]
11124 "music_lyric" | "当前歌词" | "現在歌詞" | "현재가사" | "เนื้อเพลงปัจจุบัน" | "متن_ترانه_فعلی" | "كلمات_الأغنية_الحالية" | "מילות_שיר_נוכחיות" | "موجودہ_گیت_متن" | "paroles_musique" | "musik_liedtext" | "текст_песни" =>
11125 {
11126 let id = self.arg_num(&args, 0, 0.0)? as i64;
11127 let t = self.arg_num(&args, 1, 0.0)? as f32;
11128 let line = self
11129 .lyrics
11130 .get(id as usize)
11131 .map(|l| l.line_at(t).to_string())
11132 .unwrap_or_default();
11133 return Ok(Value::Str(line));
11134 },
11135 #[cfg(not(target_arch = "wasm32"))]
11136 "music_mic_pitch" | "麦克风音高" | "マイク音程" | "마이크음정" | "ระดับเสียงไมค์" | "زیروبمی_میکروفون" | "طبقة_صوت_الميكروفون" | "גובה_צליל_מיקרופון" | "مائیکروفون_پچ" | "hauteur_micro_musique" | "musik_mikrofon_tonhöhe" | "высота_тона_микрофона" =>
11137 {
11138 let hz = if let Some(mic) = self.mic.as_ref() {
11139 let s = mic.latest_samples();
11140 let rate = mic.sample_rate();
11141 ling_music::pitch::detect(&s, rate).unwrap_or(0.0)
11142 } else {
11143 0.0
11144 };
11145 return Ok(Value::Number(hz as f64));
11146 },
11147 #[cfg(not(target_arch = "wasm32"))]
11148 "music_note_name" | "音名" | "音名称" | "음이름" | "ชื่อโน้ต" | "نام_نت" | "اسم_النغمة" | "שם_תו" | "نوٹ_نام" | "nom_note_musique" | "musik_notenname" | "имя_ноты_музыки" =>
11149 {
11150 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11151 return Ok(Value::Str(ling_music::note::hz_to_name(hz)));
11152 },
11153 #[cfg(not(target_arch = "wasm32"))]
11154 "music_hz" | "音符频率" | "音符周波数" | "음표주파수" | "ความถี่โน้ต" | "فرکانس_نت" | "تردد_النغمة" | "תדר_תו" | "نوٹ_ہرٹز" | "hz_musique" | "musik_hz" | "музыка_гц" =>
11155 {
11156 let midi = self.pitch_arg(&args, 0, 69);
11157 return Ok(Value::Number(
11158 ling_music::note::midi_to_hz(midi as f32) as f64
11159 ));
11160 },
11161 #[cfg(not(target_arch = "wasm32"))]
11162 "music_pitch_score" | "音准评分" | "音程スコア" | "음정점수" | "คะแนนเสียง" | "امتیاز_زیروبمی" | "درجة_طبقة_الصوت" | "ציון_גובה_צליל" | "پچ_اسکور" | "score_hauteur_musique" | "musik_tonhöhen_punktzahl" | "счёт_высоты_тона" =>
11163 {
11164 let hz = self.arg_num(&args, 0, 0.0)? as f32;
11165 let target = self.arg_num(&args, 1, 0.0)? as f32;
11166 return Ok(Value::Number(
11167 ling_music::karaoke::pitch_score(hz, target) as f64
11168 ));
11169 },
11170
11171 #[cfg(not(target_arch = "wasm32"))]
11173 "music_midi_load" | "载入MIDI" | "MIDI読込" | "미디로드" | "โหลดมิดี" | "بارگذاری_MIDI" | "تحميل_MIDI" | "טעינת_MIDI" | "MIDI_لوڈ" | "charger_midi_musique" | "musik_midi_laden" | "загрузить_midi_музыки" =>
11174 {
11175 let path = self.arg_str(&args, 0, "");
11176 let resolved = if std::path::Path::new(&path).exists() {
11177 path.clone()
11178 } else if let Some(d) = &self.source_dir {
11179 d.join(&path).to_string_lossy().into_owned()
11180 } else {
11181 path.clone()
11182 };
11183 match ling_music::midi::load(&resolved) {
11184 Ok(m) => {
11185 let id = self.midis.len();
11186 self.midis.push(m);
11187 return Ok(Value::Number(id as f64));
11188 },
11189 Err(e) => {
11190 eprintln!("music_midi_load failed ({path}): {e}");
11191 return Ok(Value::Number(-1.0));
11192 },
11193 }
11194 },
11195 #[cfg(not(target_arch = "wasm32"))]
11196 "music_midi_count" | "MIDI数量" | "MIDI数" | "미디수" | "จำนวนมิดี" | "تعداد_MIDI" | "عدد_MIDI" | "מספר_MIDI" | "MIDI_تعداد" | "nombre_midi_musique" | "musik_midi_anzahl" | "число_midi_музыки" =>
11197 {
11198 let id = self.arg_num(&args, 0, 0.0)? as i64;
11199 let n = self
11200 .midis
11201 .get(id as usize)
11202 .map(|m| m.notes.len())
11203 .unwrap_or(0);
11204 return Ok(Value::Number(n as f64));
11205 },
11206 #[cfg(not(target_arch = "wasm32"))]
11208 "music_midi_notes" | "MIDI音符" | "MIDIノート" | "미디음표" | "โน้ตมิดี" | "نتهای_MIDI" | "نغمات_MIDI" | "תווי_MIDI" | "MIDI_نوٹس" | "notes_midi_musique" | "musik_midi_noten" | "ноты_midi_музыки" =>
11209 {
11210 let id = self.arg_num(&args, 0, 0.0)? as i64;
11211 let mut out = Vec::new();
11212 if let Some(m) = self.midis.get(id as usize) {
11213 for n in &m.notes {
11214 out.push(Value::Number(n.time as f64));
11215 out.push(Value::Number(n.midi as f64));
11216 }
11217 }
11218 return Ok(Value::List(Rc::new(out)));
11219 },
11220 #[cfg(not(target_arch = "wasm32"))]
11222 "music_midi_bars" | "MIDI音条" | "MIDIバー" | "미디바" | "แท่งมิดี" | "میلههای_MIDI" | "أعمدة_MIDI" | "עמודות_MIDI" | "MIDI_بارز" | "mesures_midi_musique" | "musik_midi_takte" | "такты_midi_музыки" =>
11223 {
11224 let id = self.arg_num(&args, 0, 0.0)? as i64;
11225 let mut out = Vec::new();
11226 if let Some(m) = self.midis.get(id as usize) {
11227 for n in &m.notes {
11228 out.push(Value::Number(n.time as f64));
11229 out.push(Value::Number(n.midi as f64));
11230 out.push(Value::Number(n.dur as f64));
11231 }
11232 }
11233 return Ok(Value::List(Rc::new(out)));
11234 },
11235
11236 #[cfg(not(target_arch = "wasm32"))]
11238 "music_fft" | "音乐频谱" | "音楽スペクトル" | "음악스펙트럼" | "สเปกตรัมเพลง" | "طیف_موسیقی" | "طيف_الموسيقى" | "ספקטרום_מוזיקה" | "میوزک_اسپیکٹرم" | "fft_musique" | "musik_fft" | "fft_музыки" =>
11239 {
11240 let id = self.arg_num(&args, 0, 0.0)? as i64;
11241 let nbands = self.arg_num(&args, 1, 16.0)? as usize;
11242 let pos = self.music.as_ref().map(|m| m.position()).unwrap_or(0.0);
11243 if let Some(t) = self.tracks.get(id as usize) {
11244 let idx = (pos * t.rate as f32) as usize;
11245 let end = (idx + 2048).min(t.mono.len());
11246 if end > idx + 64 {
11247 self.fft.borrow_mut().push_samples(&t.mono[idx..end]);
11248 }
11249 }
11250 let bands = self.fft.borrow().freq_bands(nbands);
11251 return Ok(Value::List(Rc::new(
11252 bands.into_iter().map(|x| Value::Number(x as f64)).collect(),
11253 )));
11254 },
11255
11256 #[cfg(not(target_arch = "wasm32"))]
11258 "audio_stop_sfx" | "停止音效" | "効果音停止" | "효과음정지" | "หยุดเอฟเฟกต์ทั้งหมด" | "توقف_همه_جلوهها" | "أوقف_كل_المؤثرات" | "עצור_כל_האפקטים" | "تمام_ایفیکٹ_روکو" =>
11259 {
11260 if let Some(a) = &self.audio {
11261 a.stop_all_sfx();
11262 }
11263 return Ok(Value::Unit);
11264 },
11265 #[cfg(not(target_arch = "wasm32"))]
11267 "audio_sfx" | "音效" | "空間効果音" | "공간효과음" | "เสียงเอฟเฟกต์" | "جلوه_صوتی" | "مؤثرات_صوتية" | "אפקט_קול" | "آواز_ایفیکٹ" | "effet_sonore" | "klangeffekt" | "звуковой_эффект" =>
11268 {
11269 let x = self.arg_num(&args, 0, 0.0)? as f32;
11270 let y = self.arg_num(&args, 1, 0.0)? as f32;
11271 let z = self.arg_num(&args, 2, 0.0)? as f32;
11272 let w = self.arg_num(&args, 3, 1.0)? as f32;
11273 let freq = self.arg_num(&args, 4, 440.0)? as f32;
11274 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11275 let dur = self.arg_num(&args, 6, 0.15)? as f32;
11276 let wave = Wave::from_name(&self.arg_str(&args, 7, "sine"));
11277 if let Some(a) = &self.audio {
11278 a.sfx(x, y, z, w, freq, amp, dur, wave);
11279 }
11280 return Ok(Value::Unit);
11281 },
11282 #[cfg(not(target_arch = "wasm32"))]
11287 "morph_note" | "โน้ตมอร์ฟ" | "变形音" | "モーフ音" | "모프음" | "نت_مورف" | "نغمة_متحولة" | "תו_מורף" | "مورف_نوٹ" =>
11288 {
11289 let x = self.arg_num(&args, 0, 0.0)? as f32;
11290 let y = self.arg_num(&args, 1, 0.0)? as f32;
11291 let z = self.arg_num(&args, 2, 0.0)? as f32;
11292 let w = self.arg_num(&args, 3, 1.0)? as f32;
11293 let freq = self.arg_num(&args, 4, 220.0)? as f32;
11294 let amp = self.arg_num(&args, 5, 0.3)? as f32;
11295 let dur = self.arg_num(&args, 6, 0.6)? as f32;
11296 let material = self.arg_num(&args, 7, 0.0)?.clamp(0.0, 3.0) as u8;
11297 let morph = self.arg_num(&args, 8, 0.0)? as f32;
11298 if let Some(a) = &self.audio {
11299 a.morph_note(x, y, z, w, freq, amp, dur, material, morph);
11300 }
11301 return Ok(Value::Unit);
11302 },
11303 #[cfg(not(target_arch = "wasm32"))]
11305 "audio_sample_load" | "载入采样" | "サンプル読込" | "샘플로드" | "โหลดตัวอย่างเสียง" | "بارگذاری_نمونه_صدا" | "تحميل_عينة_صوتية" | "טעינת_דגימת_קול" | "آواز_نمونہ_لوڈ" | "charger_échantillon" | "sample_laden" | "загрузить_семпл" =>
11306 {
11307 let path = self.arg_str(&args, 0, "");
11308 let resolved = if std::path::Path::new(&path).exists() {
11309 path.clone()
11310 } else if let Some(d) = &self.source_dir {
11311 d.join(&path).to_string_lossy().into_owned()
11312 } else {
11313 path.clone()
11314 };
11315 match ling_music::load(&resolved) {
11316 Ok(t) => {
11317 if let Some(a) = &self.audio {
11318 return Ok(Value::Number(a.add_sample(t.mono, t.rate) as f64));
11319 }
11320 return Ok(Value::Number(-1.0));
11321 },
11322 Err(e) => {
11323 eprintln!("audio_sample_load failed ({path}): {e}");
11324 return Ok(Value::Number(-1.0));
11325 },
11326 }
11327 },
11328 #[cfg(not(target_arch = "wasm32"))]
11329 "audio_sample_play" | "播放采样" | "サンプル再生" | "샘플재생" | "เล่นตัวอย่างเสียง" | "پخش_نمونه_صدا" | "تشغيل_عينة_صوتية" | "נגינת_דגימת_קול" | "آواز_نمونہ_چلاؤ" | "jouer_échantillon" | "sample_abspielen" | "играть_семпл" =>
11330 {
11331 let id = self.arg_num(&args, 0, 0.0)? as usize;
11332 let x = self.arg_num(&args, 1, 0.0)? as f32;
11333 let y = self.arg_num(&args, 2, 0.0)? as f32;
11334 let z = self.arg_num(&args, 3, 0.0)? as f32;
11335 let w = self.arg_num(&args, 4, 1.0)? as f32;
11336 let vol = self.arg_num(&args, 5, 1.0)? as f32;
11337 let looping = self.arg_num(&args, 6, 0.0)? > 0.5;
11338 let v = self
11339 .audio
11340 .as_ref()
11341 .map(|a| a.play_sample(id, x, y, z, w, vol, looping))
11342 .unwrap_or(0);
11343 return Ok(Value::Number(v as f64));
11344 },
11345 #[cfg(not(target_arch = "wasm32"))]
11346 "audio_sample_stop" | "停止采样" | "サンプル停止" | "샘플정지" | "หยุดตัวอย่างเสียง" | "توقف_نمونه_صدا" | "إيقاف_عينة_صوتية" | "עצירת_דגימת_קול" | "آواز_نمونہ_روکو" | "arrêter_échantillon" | "sample_stoppen" | "остановить_семпл" =>
11347 {
11348 let v = self.arg_num(&args, 0, 0.0)? as u32;
11349 if let Some(a) = &self.audio {
11350 a.stop_sample(v);
11351 }
11352 return Ok(Value::Unit);
11353 },
11354 #[cfg(not(target_arch = "wasm32"))]
11356 "audio_fx_delay" | "回声" | "ディレイ効果" | "딜레이" | "เสียงสะท้อน" | "افکت_تاخیر" | "صدى_تأخير" | "אפקט_עיכוב" | "تاخیر_ایفیکٹ" | "délai_audio" | "audio_verzögerung" | "звук_задержка" =>
11357 {
11358 let time = self.arg_num(&args, 0, 0.3)? as f32;
11359 let fb = self.arg_num(&args, 1, 0.3)? as f32;
11360 let mix = self.arg_num(&args, 2, 0.3)? as f32;
11361 if let Some(a) = &self.audio {
11362 a.fx_delay(time, fb, mix);
11363 }
11364 return Ok(Value::Unit);
11365 },
11366 #[cfg(not(target_arch = "wasm32"))]
11367 "audio_fx_reverb" | "混响" | "リバーブ" | "리버브" | "เสียงก้อง" | "افکت_پژواک" | "صدى_ارتداد" | "אפקט_הדהוד" | "بازگشت_آواز_ایفیکٹ" | "réverbération_audio" | "audio_nachhall" | "звук_реверберация" =>
11368 {
11369 let mix = self.arg_num(&args, 0, 0.3)? as f32;
11370 if let Some(a) = &self.audio {
11371 a.fx_reverb(mix);
11372 }
11373 return Ok(Value::Unit);
11374 },
11375 #[cfg(not(target_arch = "wasm32"))]
11376 "audio_fx_lowpass" | "低通滤波" | "ローパス" | "저역통과" | "กรองความถี่ต่ำ" | "فیلتر_پایینگذر" | "مرشح_تمرير_منخفض" | "מסנן_תדר_נמוך" | "لو_پاس_فلٹر" | "passe_bas_audio" | "audio_tiefpass" | "звук_фнч" =>
11377 {
11378 let cutoff = self.arg_num(&args, 0, 1.0)? as f32;
11379 if let Some(a) = &self.audio {
11380 a.fx_lowpass(cutoff);
11381 }
11382 return Ok(Value::Unit);
11383 },
11384
11385 #[cfg(not(target_arch = "wasm32"))]
11392 "soft_ball" | "软球" | "ソフトボール" | "소프트볼" | "ลูกบอลนุ่ม" | "توپ_نرم" | "كرة_ناعمة" | "כדור_רך" | "نرم_گیند" | "balle_molle" | "weicher_ball" | "мягкий_шар" =>
11393 {
11394 let x = self.arg_num(&args, 0, 0.)? as f32;
11395 let y = self.arg_num(&args, 1, 0.)? as f32;
11396 let z = self.arg_num(&args, 2, 0.)? as f32;
11397 let r = self.arg_num(&args, 3, 1.0)? as f32;
11398 let b = ling_physics::soft::SoftBody::sphere(
11399 ling_physics::Vec3::new(x, y, z),
11400 r,
11401 8,
11402 12,
11403 1.0,
11404 );
11405 let id = self.soft_bodies.len();
11406 self.soft_bodies.push(b);
11407 return Ok(Value::Number(id as f64));
11408 },
11409 #[cfg(not(target_arch = "wasm32"))]
11410 "soft_step" | "软体步进" | "ソフト更新" | "소프트스텝" | "ก้าวนุ่ม" | "گام_نرم_جسم" | "خطوة_ناعمة" | "צעד_רך" | "نرم_قدم" | "pas_mou" | "weicher_schritt" | "мягкий_шаг" =>
11411 {
11412 let id = self.arg_num(&args, 0, 0.)? as usize;
11413 let dt = self.arg_num(&args, 1, 0.016)? as f32;
11414 let gy = self.arg_num(&args, 2, 15.0)? as f32;
11415 if let Some(b) = self.soft_bodies.get_mut(id) {
11416 b.integrate(dt, ling_physics::Vec3::new(0.0, gy, 0.0), 4);
11417 }
11418 return Ok(Value::Unit);
11419 },
11420 #[cfg(not(target_arch = "wasm32"))]
11421 "soft_bounce" | "软体落地" | "ソフト着地" | "소프트바운스" | "เด้งนุ่ม" | "جهش_نرم" | "ارتداد_ناعم" | "קפיצה_רכה" | "نرم_اچھال" | "rebond_mou" | "weicher_abprall" | "мягкий_отскок" =>
11422 {
11423 let id = self.arg_num(&args, 0, 0.)? as usize;
11424 let fy = self.arg_num(&args, 1, 0.)? as f32;
11425 let rest = self.arg_num(&args, 2, 0.5)? as f32;
11426 if let Some(b) = self.soft_bodies.get_mut(id) {
11427 b.floor_collision(fy, rest);
11428 }
11429 return Ok(Value::Unit);
11430 },
11431 #[cfg(not(target_arch = "wasm32"))]
11432 "soft_contain" | "软体边界" | "ソフト箱" | "소프트경계" | "กล่องนุ่ม" | "محفظه_نرم" | "احتواء_ناعم" | "הכלה_רכה" | "نرم_احاطہ" | "contenir_mou" | "weiche_eindämmung" | "мягкое_сдерживание" =>
11433 {
11434 let id = self.arg_num(&args, 0, 0.)? as usize;
11435 let nx = self.arg_num(&args, 1, -5.)? as f32;
11436 let ny = self.arg_num(&args, 2, -5.)? as f32;
11437 let nz = self.arg_num(&args, 3, -5.)? as f32;
11438 let mx = self.arg_num(&args, 4, 5.)? as f32;
11439 let my = self.arg_num(&args, 5, 5.)? as f32;
11440 let mz = self.arg_num(&args, 6, 5.)? as f32;
11441 let rest = self.arg_num(&args, 7, 0.6)? as f32;
11442 if let Some(b) = self.soft_bodies.get_mut(id) {
11443 b.contain(
11444 ling_physics::Vec3::new(nx, ny, nz),
11445 ling_physics::Vec3::new(mx, my, mz),
11446 rest,
11447 );
11448 }
11449 return Ok(Value::Unit);
11450 },
11451 #[cfg(not(target_arch = "wasm32"))]
11452 "soft_kick" | "软体踢" | "ソフト衝撃" | "소프트킥" | "เตะนุ่ม" | "ضربه_نرم" | "ركلة_ناعمة" | "בעיטה_רכה" | "نرم_ٹھوکر" | "coup_mou" | "weicher_stoß" | "мягкий_удар" =>
11453 {
11454 let id = self.arg_num(&args, 0, 0.)? as usize;
11455 let dx = self.arg_num(&args, 1, 0.)? as f32;
11456 let dy = self.arg_num(&args, 2, 0.)? as f32;
11457 let dz = self.arg_num(&args, 3, 0.)? as f32;
11458 let s = self.arg_num(&args, 4, 0.1)? as f32;
11459 if let Some(b) = self.soft_bodies.get_mut(id) {
11460 b.kick(ling_physics::Vec3::new(dx, dy, dz), s);
11461 }
11462 return Ok(Value::Unit);
11463 },
11464 #[cfg(not(target_arch = "wasm32"))]
11467 "soft_spin" | "软体自旋" | "ソフト回転" | "소프트회전" | "หมุนนุ่ม" | "چرخش_نرم" | "دوران_ناعم" | "סיבוב_רך" | "نرم_گھماؤ" | "rotation_molle" | "weicher_spin" | "мягкое_вращение" =>
11468 {
11469 let id = self.arg_num(&args, 0, 0.)? as usize;
11470 let ax = self.arg_num(&args, 1, 0.)? as f32;
11471 let ay = self.arg_num(&args, 2, 0.)? as f32;
11472 let az = self.arg_num(&args, 3, 0.)? as f32;
11473 let rate = self.arg_num(&args, 4, 0.1)? as f32;
11474 if let Some(b) = self.soft_bodies.get_mut(id) {
11475 b.spin(ling_physics::Vec3::new(ax, ay, az), rate);
11476 }
11477 return Ok(Value::Unit);
11478 },
11479 #[cfg(not(target_arch = "wasm32"))]
11480 "soft_deform" | "形变量" | "変形量" | "변형량" | "ความบิดเบี้ยว" | "تغییرشکل_نرم" | "تشوه_ناعم" | "עיוות_רך" | "نرم_بگاڑ" | "déformer_mou" | "weiches_verformen" | "мягкая_деформация" =>
11481 {
11482 let id = self.arg_num(&args, 0, 0.)? as usize;
11483 let d = self
11484 .soft_bodies
11485 .get(id)
11486 .map(|b| b.deformation())
11487 .unwrap_or(0.0);
11488 return Ok(Value::Number(d as f64));
11489 },
11490 #[cfg(not(target_arch = "wasm32"))]
11493 "soft_angular_speed"
11494 | "软体角速"
11495 | "ソフト角速度"
11496 | "소프트각속도"
11497 | "ความเร็วเชิงมุมนุ่ม" | "سرعت_زاویهای_نرم" | "سرعة_زاوية_ناعمة" | "מהירות_זוויתית_רכה" | "نرم_زاویائی_رفتار" | "vitesse_angulaire_molle" | "weiche_winkelgeschwindigkeit" | "мягкая_угловая_скорость" => {
11498 let id = self.arg_num(&args, 0, 0.)? as usize;
11499 let w = self
11500 .soft_bodies
11501 .get(id)
11502 .map(|b| b.angular_speed())
11503 .unwrap_or(0.0);
11504 return Ok(Value::Number(w as f64));
11505 },
11506 #[cfg(not(target_arch = "wasm32"))]
11507 "soft_centroid" | "软体质心" | "ソフト重心" | "소프트중심" | "จุดศูนย์กลางนุ่ม" | "مرکز_جرم_نرم" | "مركز_ثقل_ناعم" | "מרכז_כובד_רך" | "نرم_مرکز_ثقل" | "centroïde_mou" | "weicher_schwerpunkt" | "мягкий_центроид" =>
11508 {
11509 let id = self.arg_num(&args, 0, 0.)? as usize;
11510 let c = self
11511 .soft_bodies
11512 .get(id)
11513 .map(|b| b.centroid())
11514 .unwrap_or(ling_physics::Vec3::ZERO);
11515 return Ok(Value::List(Rc::new(vec![
11516 Value::Number(c.x as f64),
11517 Value::Number(c.y as f64),
11518 Value::Number(c.z as f64),
11519 ])));
11520 },
11521 #[cfg(not(target_arch = "wasm32"))]
11523 "soft_nodes" | "软体节点" | "ソフト節点" | "소프트노드" | "จุดนุ่ม" | "گرههای_نرم" | "عقد_ناعمة" | "צמתי_רך" | "نرم_نوڈز" | "nœuds_mous" | "weiche_knoten" | "мягкие_узлы" =>
11524 {
11525 let id = self.arg_num(&args, 0, 0.)? as usize;
11526 let mut out = Vec::new();
11527 if let Some(b) = self.soft_bodies.get(id) {
11528 for n in &b.nodes {
11529 out.push(Value::Number(n.pos.x as f64));
11530 out.push(Value::Number(n.pos.y as f64));
11531 out.push(Value::Number(n.pos.z as f64));
11532 }
11533 }
11534 return Ok(Value::List(Rc::new(out)));
11535 },
11536
11537 #[cfg(not(target_arch = "wasm32"))]
11539 "rb_add" | "刚体添加" | "剛体追加" | "강체추가" | "เพิ่มวัตถุแข็ง" | "افزودن_جسم_صلب" | "أضف_جسما_صلبا" | "הוסף_גוף_קשיח" | "سخت_جسم_شامل_کرو" | "ajouter_corps_rigide" | "starrkörper_hinzufügen" | "добавить_твёрдое_тело" =>
11540 {
11541 let x = self.arg_num(&args, 0, 0.)? as f32;
11542 let y = self.arg_num(&args, 1, 0.)? as f32;
11543 let z = self.arg_num(&args, 2, 0.)? as f32;
11544 let mass = self.arg_num(&args, 3, 1.0)? as f32;
11545 let mut b =
11546 ling_physics::rigid::RigidBody::new(ling_physics::Vec3::new(x, y, z), mass);
11547 b.restitution = 0.6;
11548 return Ok(Value::Number(self.rigid_world.add(b) as f64));
11549 },
11550 #[cfg(not(target_arch = "wasm32"))]
11551 "rb_torque" | "扭矩" | "トルク" | "토크" | "แรงบิด" | "گشتاور" | "عزم_دوران" | "מומנט" | "ٹارک" | "couple_corps_rigide" | "starrkörper_drehmoment" | "крутящий_момент_твёрдого_тела" => {
11552 let i = self.arg_num(&args, 0, 0.)? as usize;
11553 let tx = self.arg_num(&args, 1, 0.)? as f32;
11554 let ty = self.arg_num(&args, 2, 0.)? as f32;
11555 let tz = self.arg_num(&args, 3, 0.)? as f32;
11556 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11557 b.apply_torque(ling_physics::Vec3::new(tx, ty, tz));
11558 }
11559 return Ok(Value::Unit);
11560 },
11561 #[cfg(not(target_arch = "wasm32"))]
11562 "rb_spin" | "自旋" | "スピン" | "스핀" | "หมุน" | "چرخش_جسم_صلب" | "دوران_جسم_صلب" | "סיבוב_גוף_קשיח" | "سخت_جسم_گھماؤ" | "spin_corps_rigide" | "starrkörper_spin" | "вращение_твёрдого_тела" => {
11563 let i = self.arg_num(&args, 0, 0.)? as usize;
11564 let wx = self.arg_num(&args, 1, 0.)? as f32;
11565 let wy = self.arg_num(&args, 2, 0.)? as f32;
11566 let wz = self.arg_num(&args, 3, 0.)? as f32;
11567 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11568 b.apply_spin(ling_physics::Vec3::new(wx, wy, wz));
11569 }
11570 return Ok(Value::Unit);
11571 },
11572 #[cfg(not(target_arch = "wasm32"))]
11573 "rb_impulse" | "刚体冲量" | "剛体インパルス" | "강체충격" | "แรงดลแข็ง" | "ضربه_جسم_صلب" | "دفعة_جسم_صلب" | "דחף_גוף_קשיח" | "سخت_جسم_دھکا" | "impulsion_corps_rigide" | "starrkörper_impuls" | "импульс_твёрдого_тела" =>
11574 {
11575 let i = self.arg_num(&args, 0, 0.)? as usize;
11576 let ix = self.arg_num(&args, 1, 0.)? as f32;
11577 let iy = self.arg_num(&args, 2, 0.)? as f32;
11578 let iz = self.arg_num(&args, 3, 0.)? as f32;
11579 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11580 b.apply_impulse(ling_physics::Vec3::new(ix, iy, iz));
11581 }
11582 return Ok(Value::Unit);
11583 },
11584 #[cfg(not(target_arch = "wasm32"))]
11585 "rb_floor" | "刚体落地" | "剛体着地" | "강체바닥" | "พื้นแข็ง" | "کف_جسم_صلب" | "أرضية_جسم_صلب" | "רצפת_גוף_קשיח" | "سخت_جسم_فرش" | "sol_corps_rigide" | "starrkörper_boden" | "пол_твёрдого_тела" =>
11586 {
11587 let i = self.arg_num(&args, 0, 0.)? as usize;
11588 let fy = self.arg_num(&args, 1, 0.)? as f32;
11589 let rest = self.arg_num(&args, 2, 0.6)? as f32;
11590 let fric = self.arg_num(&args, 3, 0.6)? as f32;
11591 if let Some(b) = self.rigid_world.bodies.get_mut(i) {
11592 b.bounce_floor(fy, rest, fric);
11593 }
11594 return Ok(Value::Unit);
11595 },
11596 #[cfg(not(target_arch = "wasm32"))]
11597 "rb_gravity" | "刚体重力" | "剛体重力" | "강체중력" | "แรงโน้มถ่วงแข็ง" | "گرانش_جسم_صلب" | "جاذبية_جسم_صلب" | "כבידת_גוף_קשיח" | "سخت_جسم_کشش_ثقل" | "gravité_corps_rigide" | "starrkörper_schwerkraft" | "гравитация_твёрдого_тела" =>
11598 {
11599 let gx = self.arg_num(&args, 0, 0.)? as f32;
11600 let gy = self.arg_num(&args, 1, 9.81)? as f32;
11601 let gz = self.arg_num(&args, 2, 0.)? as f32;
11602 self.rigid_world.gravity = ling_physics::Vec3::new(gx, gy, gz);
11603 return Ok(Value::Unit);
11604 },
11605 #[cfg(not(target_arch = "wasm32"))]
11606 "rb_step" | "刚体步进" | "剛体更新" | "강체스텝" | "ก้าวแข็ง" | "گام_جسم_صلب" | "خطوة_جسم_صلب" | "צעד_גוף_קשיח" | "سخت_جسم_قدم" | "pas_corps_rigide" | "starrkörper_schritt" | "шаг_твёрдого_тела" =>
11607 {
11608 let dt = self.arg_num(&args, 0, 0.016)? as f32;
11609 self.rigid_world.step(dt);
11610 return Ok(Value::Unit);
11611 },
11612 #[cfg(not(target_arch = "wasm32"))]
11613 "rb_pos" | "刚体位置" | "剛体位置" | "강체위치" | "ตำแหน่งแข็ง" | "موقعیت_جسم_صلب" | "موضع_جسم_صلب" | "מיקום_גוף_קשיח" | "سخت_جسم_مقام" | "position_corps_rigide" | "starrkörper_position" | "позиция_твёрдого_тела" =>
11614 {
11615 let i = self.arg_num(&args, 0, 0.)? as usize;
11616 let p = self
11617 .rigid_world
11618 .bodies
11619 .get(i)
11620 .map(|b| b.pos)
11621 .unwrap_or(ling_physics::Vec3::ZERO);
11622 return Ok(Value::List(Rc::new(vec![
11623 Value::Number(p.x as f64),
11624 Value::Number(p.y as f64),
11625 Value::Number(p.z as f64),
11626 ])));
11627 },
11628 #[cfg(not(target_arch = "wasm32"))]
11629 "rb_rot" | "刚体旋转" | "剛体回転" | "강체회전" | "การหมุนแข็ง" | "چرخش_جسم_صلب" | "دوران_وضعية_جسم_صلب" | "סיבוב_גוף_קשיח" | "سخت_جسم_گردش" | "rotation_corps_rigide" | "starrkörper_rotation" | "поворот_твёрдого_тела" =>
11630 {
11631 let i = self.arg_num(&args, 0, 0.)? as usize;
11632 let q = self
11633 .rigid_world
11634 .bodies
11635 .get(i)
11636 .map(|b| b.orientation)
11637 .unwrap_or(ling_physics::Quat::IDENTITY);
11638 return Ok(Value::List(Rc::new(vec![
11639 Value::Number(q.x as f64),
11640 Value::Number(q.y as f64),
11641 Value::Number(q.z as f64),
11642 Value::Number(q.w as f64),
11643 ])));
11644 },
11645
11646 #[cfg(not(target_arch = "wasm32"))]
11648 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" | "بارگذاری_مش" | "حمّل_شبكة" | "טען_רשת" | "میش_لوڈ" =>
11649 {
11650 let path = self.arg_str(&args, 0, "");
11651 let resolved = if std::path::Path::new(&path).exists() {
11652 path.clone()
11653 } else if let Some(d) = &self.source_dir {
11654 d.join(&path).to_string_lossy().into_owned()
11655 } else {
11656 path.clone()
11657 };
11658 let bytes = match std::fs::read(&resolved) {
11659 Ok(b) => b,
11660 Err(e) => {
11661 eprintln!("mesh_load failed ({path}): {e}");
11662 return Ok(Value::Number(-1.0));
11663 },
11664 };
11665 if bytes.len() < 16 || &bytes[0..4] != b"LMSH" {
11666 eprintln!("mesh_load: bad header ({path})");
11667 return Ok(Value::Number(-1.0));
11668 }
11669 let rd4 =
11670 |o: usize| -> [u8; 4] { [bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]] };
11671 let height = f32::from_le_bytes(rd4(8));
11672 let ntri = u32::from_le_bytes(rd4(12)) as usize;
11673 let need = 16usize.saturating_add(ntri.saturating_mul(9 * 4 + 3));
11674 if bytes.len() < need {
11675 eprintln!("mesh_load: truncated ({path})");
11676 return Ok(Value::Number(-1.0));
11677 }
11678 let mut pos = Vec::with_capacity(ntri * 3);
11679 let mut col = Vec::with_capacity(ntri);
11680 let mut off = 16usize;
11681 for _ in 0..ntri {
11682 for _k in 0..3 {
11683 let x = f32::from_le_bytes(rd4(off));
11684 let y = f32::from_le_bytes(rd4(off + 4));
11685 let z = f32::from_le_bytes(rd4(off + 8));
11686 off += 12;
11687 pos.push([x, y, z]);
11688 }
11689 col.push([bytes[off], bytes[off + 1], bytes[off + 2]]);
11690 off += 3;
11691 }
11692 eprintln!("mesh_load: {} ({} tris, h={:.2})", path, ntri, height);
11693 let id = self.meshes.len();
11694 self.meshes
11695 .push(crate::gfx::shapes::ColorMesh { pos, col, height });
11696 return Ok(Value::Number(id as f64));
11697 },
11698 #[cfg(target_arch = "wasm32")]
11699 "mesh_load" | "โหลดเมช" | "载入网格" | "メッシュ読込" | "메시로드" | "بارگذاری_مش" | "حمّل_شبكة" | "טען_רשת" | "میش_لوڈ" =>
11700 {
11701 return Ok(Value::Number(-1.0));
11704 },
11705 #[cfg(not(target_arch = "wasm32"))]
11706 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" | "رسم_مش_رنگی" | "ارسم_شبكة_ملونة" | "צייר_רשת_צבעונית" | "رنگین_میش_کھینچو" =>
11707 {
11708 let id = self.arg_num(&args, 0, 0.)? as usize;
11710 let cx = self.arg_num(&args, 1, 0.)? as f32;
11711 let cy = self.arg_num(&args, 2, 0.)? as f32;
11712 let cz = self.arg_num(&args, 3, 0.)? as f32;
11713 let sc = self.arg_num(&args, 4, 1.)? as f32;
11714 let yaw = self.arg_num(&args, 5, 0.)? as f32;
11715 let sway = self.arg_num(&args, 6, 0.)? as f32;
11716 let arm = self.arg_num(&args, 7, 0.)? as f32;
11717 let lean = self.arg_num(&args, 8, 0.)? as f32;
11718 let leg = self.arg_num(&args, 9, 0.)? as f32;
11719 let tuck = self.arg_num(&args, 10, 0.)? as f32;
11720 if id < self.meshes.len() {
11721 let m = &self.meshes[id];
11722 let mut gfx = self.gfx.borrow_mut();
11723 gfx.draw_color_mesh(m, cx, cy, cz, sc, yaw, sway, arm, lean, leg, tuck);
11724 }
11725 return Ok(Value::Unit);
11726 },
11727 #[cfg(target_arch = "wasm32")]
11728 "mesh_draw" | "วาดเมชสี" | "绘制网格" | "メッシュ描画" | "메시그리기" | "رسم_مش_رنگی" | "ارسم_شبكة_ملونة" | "צייר_רשת_צבעונית" | "رنگین_میش_کھینچو" =>
11729 {
11730 return Ok(Value::Unit);
11731 },
11732
11733 "liquid_new" | "新建液体" | "液体新規" | "액체생성" | "สร้างของเหลว" | "مایع_جدید" | "سائل_جديد" | "נוזל_חדש" | "نیا_مائع" | "nouveau_liquide" | "neue_flüssigkeit" | "новая_жидкость" =>
11735 {
11736 let w = self.arg_num(&args, 0, 64.)? as usize;
11737 let h = self.arg_num(&args, 1, 64.)? as usize;
11738 let id = self.liquids.len();
11739 self.liquids
11740 .push(ling_physics::liquid::LiquidGrid::new(w, h));
11741 return Ok(Value::Number(id as f64));
11742 },
11743 "liquid_set_colors" | "液体颜色" | "液体配色" | "액체색상" | "สีของเหลว" | "تنظیم_رنگ_مایع" | "عيّن_ألوان_السائل" | "קבע_צבעי_נוזל" | "مائع_رنگ_مقرر_کرو" =>
11744 {
11745 let id = self.arg_num(&args, 0, 0.)? as usize;
11746 let wr = self.arg_num(&args, 1, 40.)? as f32;
11747 let wg = self.arg_num(&args, 2, 110.)? as f32;
11748 let wb = self.arg_num(&args, 3, 235.)? as f32;
11749 let or_ = self.arg_num(&args, 4, 240.)? as f32;
11750 let og = self.arg_num(&args, 5, 175.)? as f32;
11751 let ob = self.arg_num(&args, 6, 45.)? as f32;
11752 if let Some(g) = self.liquids.get_mut(id) {
11753 g.set_colors(wr, wg, wb, or_, og, ob);
11754 }
11755 return Ok(Value::Unit);
11756 },
11757 "liquid_splat" | "液体注入" | "液体追加" | "액체분사" | "หยดของเหลว" | "پاشش_مایع" | "بقعة_سائل" | "התזת_נוזל" | "مائع_چھینٹا" | "éclaboussure_liquide" | "flüssigkeit_spritzer" | "брызги_жидкости" =>
11758 {
11759 let id = self.arg_num(&args, 0, 0.)? as usize;
11760 let x = self.arg_num(&args, 1, 0.)? as f32;
11761 let y = self.arg_num(&args, 2, 0.)? as f32;
11762 let kind = self.arg_num(&args, 3, 0.)? as i32;
11763 let amt = self.arg_num(&args, 4, 1.0)? as f32;
11764 let rad = self.arg_num(&args, 5, 4.0)? as f32;
11765 if let Some(g) = self.liquids.get_mut(id) {
11766 g.splat(x, y, kind, amt, rad);
11767 }
11768 return Ok(Value::Unit);
11769 },
11770 "liquid_gravity" | "液体重力" | "液体重力ベクトル" | "액체중력" | "แรงโน้มถ่วงเหลว" | "گرانش_مایع" | "جاذبية_السائل" | "כבידת_נוזל" | "مائع_کشش_ثقل" | "gravité_liquide" | "flüssigkeit_schwerkraft" | "гравитация_жидкости" =>
11771 {
11772 let id = self.arg_num(&args, 0, 0.)? as usize;
11773 let gx = self.arg_num(&args, 1, 0.)? as f32;
11774 let gy = self.arg_num(&args, 2, 60.)? as f32;
11775 if let Some(g) = self.liquids.get_mut(id) {
11776 g.set_gravity(gx, gy);
11777 }
11778 return Ok(Value::Unit);
11779 },
11780 "liquid_step" | "液体步进" | "液体更新" | "액체스텝" | "ก้าวของเหลว" | "گام_مایع" | "خطوة_السائل" | "צעד_נוזל" | "مائع_قدم" | "pas_liquide" | "flüssigkeit_schritt" | "шаг_жидкости" =>
11781 {
11782 let id = self.arg_num(&args, 0, 0.)? as usize;
11783 let dt = self.arg_num(&args, 1, 0.016)? as f32;
11784 if let Some(g) = self.liquids.get_mut(id) {
11785 g.step(dt);
11786 }
11787 return Ok(Value::Unit);
11788 },
11789 "liquid_step_all"
11795 | "液体全步进"
11796 | "液体全更新"
11797 | "전체액체스텝"
11798 | "ก้าวของเหลวทั้งหมด" | "گام_همه_مایعات" | "خطوة_كل_السوائل" | "צעד_כל_הנוזלים" | "تمام_مائع_قدم" => {
11799 let dt = self.arg_num(&args, 0, 0.016)? as f32;
11800 ling_physics::liquid::step_all(&mut self.liquids, dt);
11801 return Ok(Value::Unit);
11802 },
11803 "liquid_rainbow" | "液体彩虹" | "液体虹" | "액체무지개" | "ของเหลวสายรุ้ง" | "مایع_رنگینکمان" | "سائل_قوس_قزح" | "נוזל_קשת" | "قوس_قزح_مائع" | "arc_en_ciel_liquide" | "flüssigkeit_regenbogen" | "радуга_жидкости" =>
11805 {
11806 let id = self.arg_num(&args, 0, 0.)? as usize;
11807 let on = self.arg_num(&args, 1, 1.0)? > 0.5;
11808 if let Some(g) = self.liquids.get_mut(id) {
11809 g.rainbow = on;
11810 }
11811 return Ok(Value::Unit);
11812 },
11813 "liquid_mix" | "液体混合" | "液体混合度" | "액체혼합" | "การผสมของเหลว" | "ترکیب_مایع" | "مزج_سائل" | "ערבוב_נוזל" | "مائع_ملاؤ" | "mélanger_liquide" | "flüssigkeit_mischen" | "смешать_жидкость" =>
11815 {
11816 let id = self.arg_num(&args, 0, 0.)? as usize;
11817 let m = self.liquids.get(id).map(|g| g.mix_amount()).unwrap_or(0.0);
11818 return Ok(Value::Number(m as f64));
11819 },
11820 #[cfg(not(target_arch = "wasm32"))]
11822 "liquid_draw" | "绘制液体" | "液体描画" | "액체그리기" | "วาดของเหลว" | "رسم_مایع" | "ارسم_سائلا" | "צייר_נוזל" | "مائع_کھینچو" | "dessiner_liquide" | "flüssigkeit_zeichnen" | "рисовать_жидкость" =>
11823 {
11824 let id = self.arg_num(&args, 0, 0.)? as usize;
11825 let sx = self.arg_num(&args, 1, 0.)? as i32;
11826 let sy = self.arg_num(&args, 2, 0.)? as i32;
11827 let scale = (self.arg_num(&args, 3, 4.)? as i32).max(1);
11828 if id < self.liquids.len() {
11829 let (gw, gh) = {
11830 let g = &self.liquids[id];
11831 (g.w, g.h)
11832 };
11833 let mut gfx = self.gfx.borrow_mut();
11834 let (w, h) = (gfx.width as i32, gfx.height as i32);
11835 let g = &self.liquids[id];
11836 for cy in 0..gh {
11837 for cx in 0..gw {
11838 let col = g.sample_rgb(cx, cy);
11839 let bx = sx + cx as i32 * scale;
11840 let by = sy + cy as i32 * scale;
11841 for dy in 0..scale {
11842 for dx in 0..scale {
11843 let px = bx + dx;
11844 let py = by + dy;
11845 if px >= 0 && py >= 0 && px < w && py < h {
11846 gfx.buffer[(py * w + px) as usize] = col;
11847 }
11848 }
11849 }
11850 }
11851 }
11852 }
11853 return Ok(Value::Unit);
11854 },
11855 "liquid_draw_surface" | "液体贴面" | "液体曲面" | "액체곡면" | "ของเหลวบนพื้นผิว" | "رسم_سطح_مایع" | "ارسم_سطح_السائل" | "צייר_משטח_נוזל" | "مائع_سطح_کھینچو" | "dessiner_surface_liquide" | "flüssigkeit_oberfläche_zeichnen" | "рисовать_поверхность_жидкости" =>
11858 {
11859 #[cfg(not(target_arch = "wasm32"))]
11860 {
11861 let id = self.arg_num(&args, 0, 0.)? as usize;
11862 let kind = self.arg_num(&args, 1, 1.)? as i32;
11863 let cx = self.arg_num(&args, 2, 0.)? as f32;
11864 let cy = self.arg_num(&args, 3, 0.)? as f32;
11865 let cz = self.arg_num(&args, 4, 0.)? as f32;
11866 let radius = self.arg_num(&args, 5, 2.0)? as f32;
11867 let height = self.arg_num(&args, 6, 3.0)? as f32;
11868 if id < self.liquids.len() {
11869 let (gw, gh) = {
11870 let g = &self.liquids[id];
11871 (g.w, g.h)
11872 };
11873 let mut gfx = self.gfx.borrow_mut();
11874 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
11875 let cam = gfx.camera.clone();
11876 let near = -cam.zdist + 0.05;
11877 let g = &self.liquids[id];
11878 let tau = std::f32::consts::TAU;
11879 let pi = std::f32::consts::PI;
11880 let sp = |u: f32, v: f32| -> [f32; 3] {
11882 if kind == 0 {
11883 [
11884 cx + (u - 0.5) * 2.0 * radius,
11885 cy,
11886 cz + (v - 0.5) * 2.0 * radius,
11887 ]
11888 } else if kind == 2 {
11889 let th = u * tau;
11890 [
11891 cx + th.cos() * radius,
11892 cy + (v - 0.5) * height,
11893 cz + th.sin() * radius,
11894 ]
11895 } else if kind == 3 {
11896 let th = u * tau;
11897 let rr = radius * (1.0 - v);
11898 [
11899 cx + th.cos() * rr,
11900 cy + (v - 0.5) * height,
11901 cz + th.sin() * rr,
11902 ]
11903 } else if kind == 4 {
11904 let th = u * tau;
11905 let ph = v * pi * 0.5;
11906 [
11907 cx + ph.sin() * th.cos() * radius,
11908 cy - ph.cos() * radius,
11909 cz + ph.sin() * th.sin() * radius,
11910 ]
11911 } else {
11912 let th = u * tau;
11913 let ph = v * pi;
11914 [
11915 cx + ph.sin() * th.cos() * radius,
11916 cy + ph.cos() * radius,
11917 cz + ph.sin() * th.sin() * radius,
11918 ]
11919 }
11920 };
11921 let nrm = |u: f32, v: f32| -> [f32; 3] {
11922 if kind == 0 {
11923 [0.0, -1.0, 0.0]
11924 } else if kind == 2 {
11925 let th = u * tau;
11926 [th.cos(), 0.0, th.sin()]
11927 } else if kind == 3 {
11928 let th = u * tau;
11929 let s = (radius / height.max(0.01)).atan();
11930 [th.cos() * s.cos(), s.sin(), th.sin() * s.cos()]
11931 } else if kind == 4 {
11932 let th = u * tau;
11933 let ph = v * pi * 0.5;
11934 [ph.sin() * th.cos(), -ph.cos(), ph.sin() * th.sin()]
11935 } else {
11936 let th = u * tau;
11937 let ph = v * pi;
11938 [ph.sin() * th.cos(), ph.cos(), ph.sin() * th.sin()]
11939 }
11940 };
11941 let gwf = gw as f32;
11942 let ghf = gh as f32;
11943 let mut cyc = 0usize;
11944 while cyc < gh {
11945 let mut cxc = 0usize;
11946 while cxc < gw {
11947 let uc = (cxc as f32 + 0.5) / gwf;
11949 let vc = (cyc as f32 + 0.5) / ghf;
11950 let c = sp(uc, vc);
11951 let n = nrm(uc, vc);
11952 let dc = cam.depth(c[0], c[1], c[2]);
11953 if dc > near {
11954 let cull = kind != 0
11955 && cam.depth(
11956 c[0] + n[0] * 0.06,
11957 c[1] + n[1] * 0.06,
11958 c[2] + n[2] * 0.06,
11959 ) > dc;
11960 if !cull {
11961 let u0 = cxc as f32 / gwf;
11963 let u1 = (cxc + 1) as f32 / gwf;
11964 let v0 = cyc as f32 / ghf;
11965 let v1 = (cyc + 1) as f32 / ghf;
11966 let q = [sp(u0, v0), sp(u1, v0), sp(u1, v1), sp(u0, v1)];
11967 let mut poly: Vec<[f32; 2]> = Vec::with_capacity(5);
11968 let mut ok = true;
11969 for p in &q {
11970 if cam.depth(p[0], p[1], p[2]) <= near {
11971 ok = false;
11972 break;
11973 }
11974 let (sx, sy, _) = cam.project(p[0], p[1], p[2]);
11975 poly.push([sx, sy]);
11976 }
11977 if ok {
11978 let p0 = poly[0];
11979 poly.push(p0);
11980 let col = g.sample_rgb(cxc, cyc);
11981 crate::gfx::raster::fill_contours_aa(
11982 &mut gfx.buffer,
11983 w,
11984 h,
11985 col,
11986 add,
11987 std::slice::from_ref(&poly),
11988 );
11989 }
11990 }
11991 }
11992 cxc += 1;
11993 }
11994 cyc += 1;
11995 }
11996 }
11997 }
11998 #[cfg(target_arch = "wasm32")]
11999 {
12000 }
12003 return Ok(Value::Unit);
12004 },
12005 #[cfg(not(target_arch = "wasm32"))]
12008 "sparkle" | "闪光" | "きらめき" | "반짝임" | "ประกาย" | "درخشش" | "بريق" | "נצנוץ" | "چمک" | "scintillement" | "funkeln" | "искриться" => {
12009 let x = self.arg_num(&args, 0, 0.)? as f32;
12010 let y = self.arg_num(&args, 1, 0.)? as f32;
12011 let ww = self.arg_num(&args, 2, 200.)? as f32;
12012 let hh = self.arg_num(&args, 3, 200.)? as f32;
12013 let count = self.arg_num(&args, 4, 40.)? as i32;
12014 let t = self.arg_num(&args, 5, 0.)? as f32;
12015 let mut gfx = self.gfx.borrow_mut();
12016 let (w, h, add, color) = (gfx.width, gfx.height, gfx.blend == 1, gfx.color);
12017 let (cr, cg, cb) = (
12018 (color >> 16 & 0xFF) as f32,
12019 (color >> 8 & 0xFF) as f32,
12020 (color & 0xFF) as f32,
12021 );
12022 let mut n = 0i32;
12023 while n < count {
12024 let hsh = (n as u32).wrapping_mul(2654435761).wrapping_add(0x9E3779B9);
12025 let u = ((hsh >> 8) & 1023) as f32 / 1023.0;
12026 let v = ((hsh >> 18) & 1023) as f32 / 1023.0;
12027 let phase = (hsh & 255) as f32 / 255.0;
12028 let tw = (t * 3.0 + phase * std::f32::consts::TAU + n as f32).sin() * 0.5 + 0.5;
12029 let sz = 1.5 + tw * 5.0;
12030 let px = x + u * ww;
12031 let py = y + v * hh;
12032 let b = tw * tw; let col =
12034 (((cr * b) as u32) << 16) | (((cg * b) as u32) << 8) | ((cb * b) as u32);
12035 crate::gfx::raster::draw_line_aa(
12036 &mut gfx.buffer,
12037 w,
12038 h,
12039 col,
12040 add,
12041 px - sz,
12042 py,
12043 px + sz,
12044 py,
12045 );
12046 crate::gfx::raster::draw_line_aa(
12047 &mut gfx.buffer,
12048 w,
12049 h,
12050 col,
12051 add,
12052 px,
12053 py - sz,
12054 px,
12055 py + sz,
12056 );
12057 let d = sz * 0.55;
12058 crate::gfx::raster::draw_line_aa(
12059 &mut gfx.buffer,
12060 w,
12061 h,
12062 col,
12063 add,
12064 px - d,
12065 py - d,
12066 px + d,
12067 py + d,
12068 );
12069 crate::gfx::raster::draw_line_aa(
12070 &mut gfx.buffer,
12071 w,
12072 h,
12073 col,
12074 add,
12075 px - d,
12076 py + d,
12077 px + d,
12078 py - d,
12079 );
12080 n += 1;
12081 }
12082 return Ok(Value::Unit);
12083 },
12084
12085 #[cfg(not(target_arch = "wasm32"))]
12091 "dialog_show" | "对话显示" | "会話表示" | "대화표시" | "แสดงบทสนทนา" | "نمایش_گفتگو" | "اعرض_الحوار" | "הצג_דיאלוג" | "مکالمہ_دکھاؤ" | "afficher_dialogue" | "dialog_anzeigen" | "показать_диалог" =>
12092 {
12093 let text = self.arg_str(&args, 0, "");
12094 let cps = self.arg_num(&args, 1, 32.0)? as f32;
12095 self.dialog = Some(ling_game::dialog::Dialog::new(&text, cps));
12096 return Ok(Value::Unit);
12097 },
12098 #[cfg(not(target_arch = "wasm32"))]
12099 "dialog_step" | "对话步进" | "会話更新" | "대화스텝" | "ก้าวบทสนทนา" | "گام_گفتگو" | "خطوة_الحوار" | "צעד_דיאלוג" | "مکالمہ_قدم" | "pas_dialogue" | "dialog_schritt" | "шаг_диалога" =>
12100 {
12101 let dt = self.arg_num(&args, 0, 0.016)? as f32;
12102 if let Some(d) = self.dialog.as_mut() {
12103 d.update(dt);
12104 }
12105 return Ok(Value::Unit);
12106 },
12107 #[cfg(not(target_arch = "wasm32"))]
12108 "dialog_advance" | "对话推进" | "会話送り" | "대화진행" | "เลื่อนบทสนทนา" | "پیشروی_گفتگو" | "تقدّم_الحوار" | "קדם_דיאלוג" | "مکالمہ_آگے_بڑھاؤ" | "avancer_dialogue" | "dialog_weiter" | "продолжить_диалог" =>
12109 {
12110 if let Some(d) = self.dialog.as_mut() {
12111 d.advance();
12112 }
12113 return Ok(Value::Unit);
12114 },
12115 #[cfg(not(target_arch = "wasm32"))]
12116 "dialog_active" | "对话激活" | "会話中" | "대화중" | "บทสนทนาทำงาน" | "گفتگو_فعال" | "الحوار_نشط" | "דיאלוג_פעיל" | "مکالمہ_فعال" | "dialogue_actif" | "dialog_aktiv" | "диалог_активен" =>
12117 {
12118 let a = self
12119 .dialog
12120 .as_ref()
12121 .map(|d| !d.is_closed())
12122 .unwrap_or(false);
12123 return Ok(Value::Bool(a));
12124 },
12125 #[cfg(not(target_arch = "wasm32"))]
12126 "dialog_typing" | "对话打字" | "会話タイプ中" | "대화타이핑" | "กำลังพิมพ์บทสนทนา" | "گفتگو_در_حال_تایپ" | "الحوار_يكتب" | "דיאלוג_מקליד" | "مکالمہ_ٹائپنگ" | "dialogue_frappe" | "dialog_tippen" | "диалог_печатает" =>
12127 {
12128 use ling_game::dialog::Dialog;
12129
12130 let a = self
12131 .dialog
12132 .as_ref()
12133 .map(|d: &Dialog| !d.is_closed() && d.is_typing())
12134 .unwrap_or(false);
12135 return Ok(Value::Bool(a));
12136 },
12137 #[cfg(not(target_arch = "wasm32"))]
12138 "dialog_close" | "对话关闭" | "会話閉じる" | "대화닫기" | "ปิดบทสนทนา" | "بستن_گفتگو" | "أغلق_الحوار" | "סגור_דיאלוג" | "مکالمہ_بند" | "fermer_dialogue" | "dialog_schließen" | "закрыть_диалог" =>
12139 {
12140 self.dialog = None;
12141 return Ok(Value::Unit);
12142 },
12143 #[cfg(not(target_arch = "wasm32"))]
12145 "dialog_color" | "对话颜色" | "会話色" | "대화색" | "สีบทสนทนา" | "رنگ_گفتگو" | "لون_الحوار" | "צבע_דיאלוג" | "مکالمہ_رنگ" | "couleur_dialogue" | "dialog_farbe" | "цвет_диалога" =>
12146 {
12147 let role = (self.arg_num(&args, 0, 0.0)? as usize).min(3);
12148 let r = self.arg_num(&args, 1, 255.0)? as u32 & 0xFF;
12149 let g = self.arg_num(&args, 2, 255.0)? as u32 & 0xFF;
12150 let b = self.arg_num(&args, 3, 255.0)? as u32 & 0xFF;
12151 self.dialog_colors[role] = (r << 16) | (g << 8) | b;
12152 return Ok(Value::Unit);
12153 },
12154 #[cfg(not(target_arch = "wasm32"))]
12156 "dialog_draw" | "对话绘制" | "会話描画" | "대화그리기" | "วาดบทสนทนา" | "رسم_گفتگو" | "ارسم_الحوار" | "צייר_דיאלוג" | "مکالمہ_کھینچو" | "dessiner_dialogue" | "dialog_zeichnen" | "рисовать_диалог" =>
12157 {
12158 let x = self.arg_num(&args, 0, 40.0)? as f32;
12159 let y = self.arg_num(&args, 1, 0.0)? as f32;
12160 let ww = self.arg_num(&args, 2, 720.0)? as f32;
12161 let hh = self.arg_num(&args, 3, 150.0)? as f32;
12162 let font = self.arg_num(&args, 4, -1.0)? as i64;
12163 let t = (crate::runtime::now_secs() - self.start_time_secs) as f32;
12164 self.render_dialog(x, y, ww, hh, font, t);
12165 return Ok(Value::Unit);
12166 },
12167
12168 #[cfg(not(target_arch = "wasm32"))]
12173 "text_poll" => {
12174 const TEXT_BUFFER_MAX: usize = 240;
12175 #[cfg(windows)]
12184 {
12185 let topmost = self.gfx.borrow().topmost_window;
12186 if topmost {
12187 if !window_is_foreground(self.gfx.borrow().hwnd) {
12188 return Ok(Value::Str(self.text_buffer.clone()));
12189 }
12190 let shift = os_key_down(VK_SHIFT);
12191 let now = crate::runtime::now_secs();
12192 let mut gfx = self.gfx.borrow_mut();
12193 let back_idx = VK_BACK as usize;
12194 let back_down = os_key_down(VK_BACK);
12195 let back_was = gfx.raw_keys_prev[back_idx];
12196 let (mut back_since, mut back_fire) = (
12197 gfx.raw_keys_down_since[back_idx],
12198 gfx.raw_keys_last_fire[back_idx],
12199 );
12200 if key_repeat_fire(now, back_down, back_was, &mut back_since, &mut back_fire) {
12201 self.text_buffer.pop();
12202 }
12203 gfx.raw_keys_down_since[back_idx] = back_since;
12204 gfx.raw_keys_last_fire[back_idx] = back_fire;
12205 gfx.raw_keys_prev[back_idx] = back_down;
12206 for &vk in TEXT_POLL_VKS {
12207 let idx = (vk as usize) & 0xFF;
12208 let down = os_key_down(vk);
12209 let was = gfx.raw_keys_prev[idx];
12210 let (mut since, mut fire) =
12211 (gfx.raw_keys_down_since[idx], gfx.raw_keys_last_fire[idx]);
12212 if key_repeat_fire(now, down, was, &mut since, &mut fire) {
12213 if let Some(c) = vk_char(vk, shift) {
12214 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12215 self.text_buffer.push(c);
12216 }
12217 }
12218 }
12219 gfx.raw_keys_down_since[idx] = since;
12220 gfx.raw_keys_last_fire[idx] = fire;
12221 gfx.raw_keys_prev[idx] = down;
12222 }
12223 return Ok(Value::Str(self.text_buffer.clone()));
12224 }
12225 }
12226 let (keys, shift) = {
12227 let gfx = self.gfx.borrow();
12228 match gfx.window.as_ref() {
12229 Some(w) => (
12230 w.get_keys_pressed(minifb::KeyRepeat::Yes),
12231 w.is_key_down(minifb::Key::LeftShift)
12232 || w.is_key_down(minifb::Key::RightShift),
12233 ),
12234 None => (Vec::new(), false),
12235 }
12236 };
12237 for k in keys {
12238 if k == minifb::Key::Backspace {
12239 self.text_buffer.pop();
12240 } else if let Some(c) = key_char(k, shift) {
12241 if self.text_buffer.chars().count() < TEXT_BUFFER_MAX {
12242 self.text_buffer.push(c);
12243 }
12244 }
12245 }
12246 return Ok(Value::Str(self.text_buffer.clone()));
12247 },
12248 #[cfg(target_arch = "wasm32")]
12249 "text_poll" => {
12250 return Ok(Value::Str(self.text_buffer.clone()));
12251 },
12252 "text_get" => return Ok(Value::Str(self.text_buffer.clone())),
12253 "text_set" => {
12254 self.text_buffer = self.arg_str(&args, 0, "");
12255 return Ok(Value::Unit);
12256 },
12257 "text_clear" => {
12258 self.text_buffer.clear();
12259 return Ok(Value::Unit);
12260 },
12261 #[cfg(not(target_arch = "wasm32"))]
12263 "record_frame" => {
12264 let n = self.record_n;
12265 let (buf, w, h) = {
12266 let gfx = self.gfx.borrow();
12267 (gfx.buffer.clone(), gfx.width, gfx.height)
12268 };
12269 let _ = std::fs::create_dir_all("recordings");
12270 let mut out = Vec::with_capacity(w * h * 3 + 32);
12271 out.extend_from_slice(format!("P6\n{w} {h}\n255\n").as_bytes());
12272 for px in &buf {
12273 let p = *px;
12274 out.push((p >> 16) as u8);
12275 out.push((p >> 8) as u8);
12276 out.push(p as u8);
12277 }
12278 let _ = std::fs::write(format!("recordings/frame_{n:05}.ppm"), out);
12279 self.record_n += 1;
12280 return Ok(Value::Number(n as f64));
12281 },
12282 "record_count" => return Ok(Value::Number(self.record_n as f64)),
12283 #[cfg(not(target_arch = "wasm32"))]
12285 "screenshot" | "บันทึกภาพ" | "عکسصفحه" | "لقطة_شاشة" | "צילום_מסך" | "اسکرین_شاٹ" => {
12286 let mode = self.arg_str(&args, 0, "game");
12287 let (buf, w, h) = {
12288 let gfx = self.gfx.borrow();
12289 (gfx.buffer.clone(), gfx.width, gfx.height)
12290 };
12291 let _ = std::fs::create_dir_all("screenshots");
12292 let ts = std::time::SystemTime::now()
12293 .duration_since(std::time::UNIX_EPOCH)
12294 .map(|d| d.as_secs())
12295 .unwrap_or(0);
12296 let safe: String = mode
12297 .chars()
12298 .map(|c| if c.is_alphanumeric() { c } else { '_' })
12299 .collect();
12300 let path = format!("screenshots/ss_{ts}_{safe}_{w}x{h}.png");
12301 let mut rgb = Vec::with_capacity(w * h * 3);
12302 for px in &buf {
12303 let p = *px;
12304 rgb.push((p >> 16) as u8);
12305 rgb.push((p >> 8) as u8);
12306 rgb.push(p as u8);
12307 }
12308 if let Some(img) = image::RgbImage::from_raw(w as u32, h as u32, rgb) {
12309 let _ = img.save(&path);
12310 }
12311 return Ok(Value::Str(path));
12312 },
12313 #[cfg(not(target_arch = "wasm32"))]
12317 "mic_capture" => {
12318 if let Some(mic) = self.mic.as_ref() {
12319 let s = mic.latest_samples();
12320 self.mic_buffer.extend_from_slice(&s);
12321 let cap = 96_000usize; if self.mic_buffer.len() > cap {
12323 let drop = self.mic_buffer.len() - cap;
12324 self.mic_buffer.drain(0..drop);
12325 }
12326 }
12327 return Ok(Value::Number(self.mic_buffer.len() as f64));
12328 },
12329 #[cfg(not(target_arch = "wasm32"))]
12331 "mic_seed" => {
12332 let mut bytes = Vec::with_capacity(self.mic_buffer.len() * 4);
12333 for f in &self.mic_buffer {
12334 bytes.extend_from_slice(&f.to_le_bytes());
12335 }
12336 return Ok(Value::Str(hex_encode(&ling_crypto::geo::holo_hash(&bytes))));
12337 },
12338 #[cfg(not(target_arch = "wasm32"))]
12339 "mic_clear" => {
12340 self.mic_buffer.clear();
12341 return Ok(Value::Number(0.0));
12342 },
12343 #[cfg(not(target_arch = "wasm32"))]
12346 "flush_3d" | "render_3d" => {
12347 let mut gfx = self.gfx.borrow_mut();
12348 if !gfx.depth_queue.is_empty() {
12349 let w = gfx.width;
12350 let h = gfx.height;
12351 let dt = gfx.depth_test;
12352 let reset_z = gfx.zbuf_needs_clear;
12353 let (bm, ba) = (gfx.blend, gfx.alpha);
12354 let aa = gfx.antialias;
12355 let queue = std::mem::take(&mut gfx.depth_queue);
12356 {
12357 let g = &mut *gfx;
12358 let z = if dt { Some(&mut g.depth_buf) } else { None };
12359 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12360 }
12361 gfx.zbuf_needs_clear = false;
12362 gfx.depth_queue.set_state(bm, ba); }
12364 return Ok(Value::Unit);
12365 },
12366 #[cfg(target_arch = "wasm32")]
12367 "flush_3d" | "render_3d" => {
12368 let mut gfx = self.gfx.borrow_mut();
12369 if !gfx.depth_queue.is_empty() {
12370 let w = gfx.width;
12371 let h = gfx.height;
12372 let dt = gfx.depth_test;
12373 let reset_z = gfx.zbuf_needs_clear;
12374 let (bm, ba) = (gfx.blend, gfx.alpha);
12375 let aa = gfx.antialias;
12376 let queue = std::mem::take(&mut gfx.depth_queue);
12377 {
12378 let g = &mut *gfx;
12379 let z = if dt { Some(&mut g.depth_buf) } else { None };
12380 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12381 }
12382 gfx.zbuf_needs_clear = false;
12383 gfx.depth_queue.set_state(bm, ba);
12384 }
12385 return Ok(Value::Unit);
12386 },
12387
12388 "flush_post" | "post_now" | "포스트플러시" | "后期冲刷" => {
12393 let mut gfx = self.gfx.borrow_mut();
12394 if !gfx.depth_queue.is_empty() {
12395 let w = gfx.width;
12396 let h = gfx.height;
12397 let dt = gfx.depth_test;
12398 let reset_z = gfx.zbuf_needs_clear;
12399 let (bm, ba) = (gfx.blend, gfx.alpha);
12400 let aa = gfx.antialias;
12401 let queue = std::mem::take(&mut gfx.depth_queue);
12402 {
12403 let g = &mut *gfx;
12404 let z = if dt { Some(&mut g.depth_buf) } else { None };
12405 queue.flush(&mut g.buffer, z, reset_z, w, h, aa);
12406 }
12407 gfx.zbuf_needs_clear = false;
12408 gfx.depth_queue.set_state(bm, ba);
12409 }
12410 gfx.toon_post_process();
12411 gfx.post_done = true;
12412 return Ok(Value::Unit);
12413 },
12414
12415 #[cfg(not(target_arch = "wasm32"))]
12418 "screen_distort" | "บิดจอ" | "屏幕扭曲" | "画面歪み" | "화면왜곡" | "اعوجاج_صفحه" | "شوّه_الشاشة" | "עוות_מסך" | "اسکرین_ڈسٹورٹ" =>
12419 {
12420 let amount = self.arg_num(&args, 0, 8.0)? as f32;
12421 let t = self.arg_num(&args, 1, 0.0)? as f32;
12422 let step = self.arg_num(&args, 2, 1.0)?.max(1.0) as usize;
12425 let _d = std::time::Instant::now();
12426 self.gfx.borrow_mut().distort(amount, t, step);
12427 ling_phase_add(phase::DISTORT, _d.elapsed().as_nanos());
12428 return Ok(Value::Unit);
12429 },
12430
12431 "set_rim" | "设置边缘光" | "リム設定" | "림라이트" | "ตั้งขอบเรือง" | "تنظیم_نور_لبه" | "عيّن_إضاءة_الحافة" | "קבע_תאורת_קצה" | "رم_لائٹ_مقرر_کرو" | "définir_contour_lumineux" | "rimlicht_setzen" | "задать_контурный_свет" =>
12432 {
12433 let s = self.arg_num(&args, 0, 0.6)? as f32;
12434 let r = self.arg_num(&args, 1, 115.)? as f32 / 255.0;
12435 let g = self.arg_num(&args, 2, 217.)? as f32 / 255.0;
12436 let b = self.arg_num(&args, 3, 255.)? as f32 / 255.0;
12437 let mut gfx = self.gfx.borrow_mut();
12438 gfx.shade.rim = s;
12439 gfx.shade.rim_color = [r, g, b];
12440 return Ok(Value::Unit);
12441 },
12442
12443 n if crate::gfx::shapes::canon(n).is_some() => {
12452 let kind = crate::gfx::shapes::canon(n).unwrap();
12453 let cx = self.arg_num(&args, 0, 0.)? as f32;
12454 let cy = self.arg_num(&args, 1, 0.)? as f32;
12455 let cz = self.arg_num(&args, 2, 0.)? as f32;
12456 let sx = self.arg_num(&args, 3, 1.)? as f32;
12457 let sy = self.arg_num(&args, 4, 1.)? as f32;
12458 let sz = self.arg_num(&args, 5, 1.)? as f32;
12459 let rx = self.arg_num(&args, 6, 0.)? as f32;
12460 let ry = self.arg_num(&args, 7, 0.)? as f32;
12461 let rz = self.arg_num(&args, 8, 0.)? as f32;
12462 let mode = self.arg_num(&args, 9, 0.)? as i32;
12463 let e0 = self.arg_num(&args, 10, 0.)? as f32;
12464 let e1 = self.arg_num(&args, 11, 0.)? as f32;
12465 let e2 = self.arg_num(&args, 12, 0.)? as f32;
12466 if let Some(mesh) = crate::gfx::shapes::build(
12467 kind,
12468 [cx, cy, cz, sx, sy, sz, rx, ry, rz],
12469 e0,
12470 e1,
12471 e2,
12472 ) {
12473 let mut gfx = self.gfx.borrow_mut();
12474 gfx.emit_mesh(&mesh, mode);
12475 }
12476 return Ok(Value::Unit);
12477 },
12478
12479 _ => {},
12480 }
12481
12482 if let Some(field_names) = self.structs.get(name).cloned() {
12484 if args.len() != field_names.len() {
12485 return Err(EvalErr::from(format!(
12486 "{name} expects {} field(s), got {}",
12487 field_names.len(),
12488 args.len()
12489 )));
12490 }
12491 let fields = field_names.into_iter().zip(args).collect();
12492 return Ok(Value::Struct { name: name.to_string(), fields });
12493 }
12494
12495 if let Some((enum_name, arity)) = self.enum_variants.get(name).cloned() {
12497 if args.len() != arity {
12498 return Err(EvalErr::from(format!(
12499 "{name} expects {arity} value(s), got {}",
12500 args.len()
12501 )));
12502 }
12503 let variant = name.rsplit("::").next().unwrap_or(name).to_string();
12504 return Ok(Value::Variant { enum_name, variant, payload: args });
12505 }
12506
12507 #[cfg(target_arch = "wasm32")]
12508 if let Some(v) = wasm_unsupported_builtin(name) {
12509 return Ok(v);
12510 }
12511
12512 Err(EvalErr::from(format!("unknown function '{name}'")))
12513 }
12514
12515 fn call_value(&mut self, v: Value, args: Vec<Value>) -> EvalResult {
12516 match v {
12517 Value::Fn(params, body, mut captured) => {
12518 for (p, a) in params.iter().zip(args) {
12519 captured.insert(p.clone(), a);
12520 }
12521 match self.framed("<closure>", |me| me.exec_block(&body, &mut captured)) {
12522 Ok(v) => Ok(v.unwrap_or(Value::Unit)),
12523 Err(EvalErr::Return(v)) => Ok(v),
12524 Err(e) => Err(e),
12525 }
12526 },
12527 other => Err(EvalErr::from(format!("cannot call {:?}", other))),
12528 }
12529 }
12530
12531 fn call_method(&self, recv: Value, method: &str, args: Vec<Value>) -> EvalResult {
12532 match (&recv, method) {
12533 (Value::Str(s), "is_empty" | "是空") => Ok(Value::Bool(s.is_empty())),
12534 (Value::Str(s), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(s.len() as f64)),
12541 (Value::Str(s), "to_string" | "转文") => Ok(Value::Str(s.clone())),
12542 (Value::Str(s), "contains" | "包含") => {
12543 if let Some(Value::Str(sub)) = args.first() {
12544 Ok(Value::Bool(s.contains(sub.as_str())))
12545 } else {
12546 Ok(Value::Bool(false))
12547 }
12548 },
12549 (Value::Str(s), "push_str" | "推_文") => {
12550 let mut s2 = s.clone();
12551 if let Some(Value::Str(a)) = args.first() {
12552 s2.push_str(a);
12553 }
12554 Ok(Value::Str(s2))
12555 },
12556 (Value::List(v), "len" | "长" | "长度" | "長さ" | "길이" | "ความยาว") => Ok(Value::Number(v.len() as f64)),
12557 (Value::List(v), "push" | "推" | "添加" | "追加" | "추가" | "เพิ่ม") => {
12558 let mut v2: Vec<Value> = (**v).clone();
12559 if let Some(a) = args.first() {
12560 v2.push(a.clone());
12561 }
12562 Ok(Value::List(Rc::new(v2)))
12563 },
12564 (Value::Struct { fields, .. }, _) if args.is_empty() => fields
12566 .iter()
12567 .find(|(k, _)| k == method)
12568 .map(|(_, v)| v.clone())
12569 .ok_or_else(|| EvalErr::from(format!("no field '{method}' on {recv}"))),
12570 (Value::Variant { variant, .. }, "tag" | "标签" | "タグ" | "태그" | "ป้าย")
12572 if args.is_empty() =>
12573 {
12574 Ok(Value::Str(variant.clone()))
12575 },
12576 (Value::Ok(inner), _) | (Value::Err(inner), _) => Ok(*inner.clone()),
12577 _ => Err(EvalErr::from(format!("no method '{method}' on {recv}"))),
12578 }
12579 }
12580
12581 fn match_pattern(&self, pat: &Pattern, val: &Value) -> Option<Env> {
12584 match (pat, val) {
12585 (Pattern::Wildcard, _) => Some(new_env()),
12586 (Pattern::Str(s), Value::Str(v)) if s == v => Some(new_env()),
12587 (Pattern::Number(n), Value::Number(v)) if (n - v).abs() < 1e-12 => Some(new_env()),
12588 (Pattern::Bool(b), Value::Bool(v)) if b == v => Some(new_env()),
12589 (Pattern::Ident(name), _) => {
12590 let mut e = new_env();
12591 e.insert(name.clone(), val.clone());
12592 Some(e)
12593 },
12594 (Pattern::Constructor(ctor, inner_pat), _) => {
12595 let (matches, inner_val) = match (ctor.as_str(), val) {
12596 ("ok" | "好", Value::Ok(v)) => (true, Some(v.as_ref().clone())),
12597 ("bad" | "坏", Value::Err(v)) => (true, Some(v.as_ref().clone())),
12598 ("ok" | "好", v) if !matches!(v, Value::Err(_)) => (true, Some(v.clone())),
12599 _ => (false, None),
12600 };
12601 if !matches {
12602 return None;
12603 }
12604 match (inner_pat, inner_val) {
12605 (Some(p), Some(v)) => self.match_pattern(p, &v),
12606 (None, _) => Some(new_env()),
12607 (Some(p), None) => self.match_pattern(p, &Value::Unit),
12608 }
12609 },
12610 (Pattern::Variant(vname, sub_pats), Value::Variant { variant, payload, .. }) => {
12612 if vname != variant || sub_pats.len() != payload.len() {
12613 return None;
12614 }
12615 let mut bindings = new_env();
12616 for (p, v) in sub_pats.iter().zip(payload.iter()) {
12617 bindings.extend(self.match_pattern(p, v)?);
12618 }
12619 Some(bindings)
12620 },
12621 (Pattern::Variant(vname, sub), Value::Ok(v)) if (vname == "ok" || vname == "好") => {
12624 match sub.as_slice() {
12625 [] => Some(new_env()),
12626 [p] => self.match_pattern(p, v),
12627 _ => None,
12628 }
12629 },
12630 (Pattern::Variant(vname, sub), Value::Err(v))
12631 if (vname == "bad" || vname == "坏" || vname == "err") =>
12632 {
12633 match sub.as_slice() {
12634 [] => Some(new_env()),
12635 [p] => self.match_pattern(p, v),
12636 _ => None,
12637 }
12638 },
12639 _ => None,
12640 }
12641 }
12642
12643 fn value_to_iter(&self, val: Value) -> Result<Vec<Value>, EvalErr> {
12646 match val {
12647 Value::List(v) => Ok(Rc::try_unwrap(v).unwrap_or_else(|rc| (*rc).clone())),
12648 Value::Str(s) => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
12649 Value::Number(n) => Ok((0..n as i64).map(|i| Value::Number(i as f64)).collect()),
12650 other => Err(EvalErr::from(format!("cannot iterate over {:?}", other))),
12651 }
12652 }
12653
12654 pub(crate) fn is_truthy(&self, val: &Value) -> bool {
12655 match val {
12656 Value::Bool(b) => *b,
12657 Value::Unit => false,
12658 Value::Number(n) => *n != 0.0,
12659 Value::Str(s) => !s.is_empty(),
12660 Value::List(v) => !v.is_empty(),
12661 Value::Ok(_) => true,
12662 Value::Err(_) => false,
12663 Value::Fn(_, _, _) => true,
12664 Value::Struct { .. } => true,
12665 Value::Variant { .. } => true,
12666 }
12667 }
12668
12669 fn to_number(&self, val: &Value) -> Result<f64, EvalErr> {
12670 match val {
12671 Value::Number(n) => Ok(*n),
12672 Value::Str(s) => s
12673 .parse()
12674 .map_err(|_| EvalErr::from(format!("cannot convert '{s}' to number"))),
12675 other => Err(EvalErr::from(format!("expected number, got {:?}", other))),
12676 }
12677 }
12678
12679 fn arg_num(&self, args: &[Value], n: usize, default: f64) -> Result<f64, EvalErr> {
12681 match args.get(n) {
12682 Some(v) => self.to_number(v),
12683 None => Ok(default),
12684 }
12685 }
12686
12687 fn arg_str(&self, args: &[Value], n: usize, default: &str) -> String {
12688 args.get(n)
12689 .map(|v| v.to_string())
12690 .unwrap_or_else(|| default.to_string())
12691 }
12692
12693 #[allow(dead_code)]
12695 fn arg_list_f32(&self, args: &[Value], n: usize) -> Vec<f32> {
12696 match args.get(n) {
12697 Some(Value::List(v)) => v
12698 .iter()
12699 .filter_map(|x| match x {
12700 Value::Number(n) => Some(*n as f32),
12701 _ => None,
12702 })
12703 .collect(),
12704 _ => Vec::new(),
12705 }
12706 }
12707
12708 #[cfg(not(target_arch = "wasm32"))]
12711 fn color_at(&self, args: &[Value], i: usize, default: u32) -> u32 {
12712 match (args.get(i), args.get(i + 1), args.get(i + 2)) {
12713 (Some(a), Some(b), Some(c)) => {
12714 match (self.to_number(a), self.to_number(b), self.to_number(c)) {
12715 (Ok(r), Ok(g), Ok(bl)) => {
12716 ((r as u32 & 0xFF) << 16) | ((g as u32 & 0xFF) << 8) | (bl as u32 & 0xFF)
12717 },
12718 _ => default,
12719 }
12720 },
12721 _ => default,
12722 }
12723 }
12724
12725 #[cfg(not(target_arch = "wasm32"))]
12727 fn pitch_arg(&self, args: &[Value], i: usize, default: i32) -> i32 {
12728 match args.get(i) {
12729 Some(Value::Str(s)) => ling_music::note::parse_pitch(s).unwrap_or(default),
12730 Some(Value::Number(n)) => *n as i32,
12731 _ => default,
12732 }
12733 }
12734
12735 #[cfg(not(target_arch = "wasm32"))]
12737 fn mouse_now(&self) -> (f32, f32, bool) {
12738 let gfx = self.gfx.borrow();
12739 let (mx, my) = gfx
12740 .window
12741 .as_ref()
12742 .and_then(|w| w.get_mouse_pos(minifb::MouseMode::Clamp))
12743 .unwrap_or((0.0, 0.0));
12744 let down = gfx
12745 .window
12746 .as_ref()
12747 .map(|w| w.get_mouse_down(minifb::MouseButton::Left))
12748 .unwrap_or(false);
12749 (mx, my, down)
12750 }
12751
12752 #[cfg(not(target_arch = "wasm32"))]
12756 fn draw_ui(&self, d: &ling_ui::widgets::Draw) {
12757 let mut gfx = self.gfx.borrow_mut();
12758 let (w, h, add) = (gfx.width, gfx.height, gfx.blend == 1);
12759 for (c, poly) in &d.fills {
12760 crate::gfx::raster::fill_contours_aa(
12761 &mut gfx.buffer,
12762 w,
12763 h,
12764 *c,
12765 add,
12766 std::slice::from_ref(poly),
12767 );
12768 }
12769 for (c, pl) in &d.strokes {
12770 for s in pl.windows(2) {
12771 crate::gfx::raster::draw_line_aa(
12772 &mut gfx.buffer,
12773 w,
12774 h,
12775 *c,
12776 add,
12777 s[0][0],
12778 s[0][1],
12779 s[1][0],
12780 s[1][1],
12781 );
12782 }
12783 }
12784 }
12785
12786 fn tex_rect(&self, args: &[Value]) -> Result<(usize, usize, usize, usize), EvalErr> {
12788 let tx = self.arg_num(args, 0, 0.0)? as usize;
12789 let ty = self.arg_num(args, 1, 0.0)? as usize;
12790 let tw = self.arg_num(args, 2, 256.0)? as usize;
12791 let th = self.arg_num(args, 3, 256.0)? as usize;
12792 Ok((tx, ty, tw.max(1), th.max(1)))
12793 }
12794
12795 pub(crate) fn apply_binop(&self, op: &BinOp, l: Value, r: Value) -> EvalResult {
12796 match op {
12797 BinOp::Add => match (l, r) {
12798 (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
12799 (Value::Str(a), Value::Str(b)) => Ok(Value::Str(a + &b)),
12800 (Value::Str(a), b) => Ok(Value::Str(a + &b.to_string())),
12801 (a, Value::Str(b)) => Ok(Value::Str(a.to_string() + &b)),
12802 (a, b) => Err(EvalErr::from(format!("cannot add {:?} and {:?}", a, b))),
12803 },
12804 BinOp::Sub => Ok(Value::Number(self.to_number(&l)? - self.to_number(&r)?)),
12805 BinOp::Mul => Ok(Value::Number(self.to_number(&l)? * self.to_number(&r)?)),
12806 BinOp::Div => Ok(Value::Number(self.to_number(&l)? / self.to_number(&r)?)),
12807 BinOp::Rem => Ok(Value::Number(self.to_number(&l)? % self.to_number(&r)?)),
12808 BinOp::Eq => Ok(Value::Bool(values_equal(&l, &r))),
12809 BinOp::Ne => Ok(Value::Bool(!values_equal(&l, &r))),
12810 BinOp::Lt => Ok(Value::Bool(self.to_number(&l)? < self.to_number(&r)?)),
12811 BinOp::Gt => Ok(Value::Bool(self.to_number(&l)? > self.to_number(&r)?)),
12812 BinOp::Le => Ok(Value::Bool(self.to_number(&l)? <= self.to_number(&r)?)),
12813 BinOp::Ge => Ok(Value::Bool(self.to_number(&l)? >= self.to_number(&r)?)),
12814 BinOp::And => Ok(Value::Bool(self.is_truthy(&l) && self.is_truthy(&r))),
12815 BinOp::Or => Ok(Value::Bool(self.is_truthy(&l) || self.is_truthy(&r))),
12816 }
12817 }
12818
12819 fn builtin_format(&self, args: &[Value]) -> Result<String, EvalErr> {
12820 if args.is_empty() {
12821 return Ok(String::new());
12822 }
12823 let fmt = match &args[0] {
12824 Value::Str(s) => s.clone(),
12825 other => return Ok(other.to_string()),
12826 };
12827
12828 let mut result = String::new();
12829 let mut arg_idx = 1usize;
12830 let mut chars = fmt.chars().peekable();
12831 while let Some(c) = chars.next() {
12832 if c == '{' {
12833 if chars.peek() == Some(&'}') {
12834 chars.next();
12835 if arg_idx < args.len() {
12836 result.push_str(&args[arg_idx].to_string());
12837 arg_idx += 1;
12838 }
12839 } else {
12840 let mut spec = String::new();
12841 for ch in chars.by_ref() {
12842 if ch == '}' {
12843 break;
12844 }
12845 spec.push(ch);
12846 }
12847 if arg_idx < args.len() {
12848 if let Some(suffix) = spec.strip_prefix(":.") {
12849 if let Value::Number(n) = &args[arg_idx] {
12850 let prec: usize =
12851 suffix.trim_end_matches('f').parse().unwrap_or(2);
12852 result.push_str(&format!("{:.prec$}", n));
12853 arg_idx += 1;
12854 continue;
12855 }
12856 }
12857 result.push_str(&args[arg_idx].to_string());
12858 arg_idx += 1;
12859 }
12860 }
12861 } else {
12862 result.push(c);
12863 }
12864 }
12865 Ok(result)
12866 }
12867}
12868
12869#[cfg(not(target_arch = "wasm32"))]
12870#[cfg(not(target_arch = "wasm32"))]
12872fn parse_pad_button(name: &str) -> Option<ling_input::GamepadButton> {
12873 use ling_input::GamepadButton as B;
12874 Some(match name.to_ascii_lowercase().as_str() {
12875 "a" | "south" | "cross" => B::South,
12876 "b" | "east" | "circle" => B::East,
12877 "x" | "west" | "square" => B::West,
12878 "y" | "north" | "triangle" => B::North,
12879 "lb" | "l1" | "left_shoulder" => B::LeftShoulder,
12880 "rb" | "r1" | "right_shoulder" => B::RightShoulder,
12881 "lt" | "l2" | "left_trigger" => B::LeftTrigger,
12882 "rt" | "r2" | "right_trigger" => B::RightTrigger,
12883 "start" | "menu" | "options" | "démarrer" | "начать" => B::Start,
12884 "select" | "back" | "share" | "view" => B::Select,
12885 "guide" | "home" => B::Guide,
12886 "l3" | "left_stick" => B::LeftStick,
12887 "r3" | "right_stick" => B::RightStick,
12888 "up" | "dpad_up" => B::DpadUp,
12889 "down" | "dpad_down" => B::DpadDown,
12890 "left" | "dpad_left" => B::DpadLeft,
12891 "right" | "dpad_right" => B::DpadRight,
12892 _ => return None,
12893 })
12894}
12895
12896#[cfg(not(target_arch = "wasm32"))]
12897fn str_to_minifb_key(name: &str) -> Option<minifb::Key> {
12898 use minifb::Key;
12899 Some(match name {
12900 "numpad0" | "kp0" => Key::NumPad0,
12901 "numpad1" | "kp1" => Key::NumPad1,
12902 "numpad2" | "kp2" => Key::NumPad2,
12903 "numpad3" | "kp3" => Key::NumPad3,
12904 "numpad4" | "kp4" => Key::NumPad4,
12905 "numpad5" | "kp5" => Key::NumPad5,
12906 "numpad6" | "kp6" => Key::NumPad6,
12907 "numpad7" | "kp7" => Key::NumPad7,
12908 "numpad8" | "kp8" => Key::NumPad8,
12909 "numpad9" | "kp9" => Key::NumPad9,
12910 "numpad+" | "kp+" => Key::NumPadPlus,
12911 "numpad-" | "kp-" => Key::NumPadMinus,
12912 "numpad*" | "kp*" => Key::NumPadAsterisk,
12913 "numpad/" | "kp/" => Key::NumPadSlash,
12914 "left" => Key::Left,
12915 "right" => Key::Right,
12916 "up" => Key::Up,
12917 "down" => Key::Down,
12918 "space" => Key::Space,
12919 "enter" => Key::Enter,
12920 "escape" => Key::Escape,
12921 "pageup" => Key::PageUp,
12922 "pagedown" => Key::PageDown,
12923 "lshift" | "leftshift" => Key::LeftShift,
12924 "rshift" | "rightshift" => Key::RightShift,
12925 "lctrl" | "leftctrl" => Key::LeftCtrl,
12926 "rctrl" | "rightctrl" => Key::RightCtrl,
12927 "lalt" | "leftalt" => Key::LeftAlt,
12928 "ralt" | "rightalt" => Key::RightAlt,
12929 "tab" => Key::Tab,
12930 "backspace" => Key::Backspace,
12931 "delete" => Key::Delete,
12932 "insert" => Key::Insert,
12933 "home" => Key::Home,
12934 "end" => Key::End,
12935 "a" => Key::A,
12936 "b" => Key::B,
12937 "c" => Key::C,
12938 "d" => Key::D,
12939 "e" => Key::E,
12940 "f" => Key::F,
12941 "g" => Key::G,
12942 "h" => Key::H,
12943 "i" => Key::I,
12944 "j" => Key::J,
12945 "k" => Key::K,
12946 "l" => Key::L,
12947 "m" => Key::M,
12948 "n" => Key::N,
12949 "o" => Key::O,
12950 "p" => Key::P,
12951 "q" => Key::Q,
12952 "r" => Key::R,
12953 "s" => Key::S,
12954 "t" => Key::T,
12955 "u" => Key::U,
12956 "v" => Key::V,
12957 "w" => Key::W,
12958 "x" => Key::X,
12959 "y" => Key::Y,
12960 "z" => Key::Z,
12961 "0" => Key::Key0,
12962 "1" => Key::Key1,
12963 "2" => Key::Key2,
12964 "3" => Key::Key3,
12965 "4" => Key::Key4,
12966 "5" => Key::Key5,
12967 "6" => Key::Key6,
12968 "7" => Key::Key7,
12969 "8" => Key::Key8,
12970 "9" => Key::Key9,
12971 _ => return None,
12972 })
12973}
12974
12975pub(crate) fn values_equal(a: &Value, b: &Value) -> bool {
12976 match (a, b) {
12977 (Value::Number(x), Value::Number(y)) => (x - y).abs() < 1e-12,
12978 (Value::Str(x), Value::Str(y)) => x == y,
12979 (Value::Bool(x), Value::Bool(y)) => x == y,
12980 (Value::Unit, Value::Unit) => true,
12981 _ => false,
12982 }
12983}
12984
12985#[cfg(not(target_arch = "wasm32"))]
12992fn hide_console_window() {
12993 #[cfg(windows)]
12994 unsafe {
12995 extern "system" {
12996 fn GetConsoleWindow() -> isize;
12997 fn ShowWindow(hwnd: isize, nCmdShow: i32) -> i32;
12998 }
12999 let hwnd = GetConsoleWindow();
13000 if hwnd != 0 {
13001 ShowWindow(hwnd, 0); }
13003 }
13004}
13005
13006#[cfg(all(not(target_arch = "wasm32"), windows))]
13011fn make_borderless_fullscreen(hwnd: isize, screen_w: i32, screen_h: i32) {
13012 if hwnd == 0 {
13013 return;
13014 }
13015 unsafe {
13016 extern "system" {
13017 fn SetWindowLongPtrW(hwnd: isize, index: i32, new: isize) -> isize;
13018 fn SetWindowPos(
13019 hwnd: isize,
13020 insert_after: isize,
13021 x: i32,
13022 y: i32,
13023 cx: i32,
13024 cy: i32,
13025 flags: u32,
13026 ) -> i32;
13027 fn ShowWindow(hwnd: isize, cmd: i32) -> i32;
13028 }
13029 const GWL_STYLE: i32 = -16;
13030 const GWL_EXSTYLE: i32 = -20;
13031 SetWindowLongPtrW(hwnd, GWL_STYLE, 0x9000_0000isize);
13034 SetWindowLongPtrW(hwnd, GWL_EXSTYLE, 0);
13036 SetWindowPos(hwnd, -1isize, 0, 0, screen_w, screen_h, 0x0020 | 0x0040);
13038 ShowWindow(hwnd, 3); }
13040}
13041
13042#[cfg(all(not(target_arch = "wasm32"), windows))]
13056fn force_window_focus(hwnd: isize) {
13057 if hwnd == 0 {
13058 return;
13059 }
13060 unsafe {
13061 extern "system" {
13062 fn GetForegroundWindow() -> isize;
13063 fn GetWindowThreadProcessId(hwnd: isize, pid: *mut u32) -> u32;
13064 fn GetCurrentThreadId() -> u32;
13065 fn AttachThreadInput(id_attach: u32, id_attach_to: u32, attach: i32) -> i32;
13066 fn SetForegroundWindow(hwnd: isize) -> i32;
13067 fn BringWindowToTop(hwnd: isize) -> i32;
13068 fn SetFocus(hwnd: isize) -> isize;
13069 fn SetActiveWindow(hwnd: isize) -> isize;
13070 }
13071 let fg = GetForegroundWindow();
13072 if fg != 0 && fg != hwnd {
13073 let mut fg_pid: u32 = 0;
13074 let fg_tid = GetWindowThreadProcessId(fg, &mut fg_pid);
13075 let my_tid = GetCurrentThreadId();
13076 if fg_tid != 0 && fg_tid != my_tid {
13077 AttachThreadInput(my_tid, fg_tid, 1);
13078 SetForegroundWindow(hwnd);
13079 BringWindowToTop(hwnd);
13080 SetFocus(hwnd);
13081 SetActiveWindow(hwnd);
13082 AttachThreadInput(my_tid, fg_tid, 0);
13083 return;
13084 }
13085 }
13086 SetForegroundWindow(hwnd);
13087 BringWindowToTop(hwnd);
13088 SetFocus(hwnd);
13089 SetActiveWindow(hwnd);
13090 }
13091}
13092
13093#[cfg(all(not(target_arch = "wasm32"), windows))]
13098fn set_window_topmost(hwnd: isize, topmost: bool) {
13099 if hwnd == 0 {
13100 return;
13101 }
13102 unsafe {
13103 extern "system" {
13104 fn SetWindowPos(
13105 hwnd: isize,
13106 insert_after: isize,
13107 x: i32,
13108 y: i32,
13109 cx: i32,
13110 cy: i32,
13111 flags: u32,
13112 ) -> i32;
13113 }
13114 let insert_after: isize = if topmost { -1 } else { -2 }; SetWindowPos(hwnd, insert_after, 0, 0, 0, 0, 0x0002 | 0x0001 | 0x0010);
13118 }
13119}
13120
13121#[cfg(not(target_arch = "wasm32"))]
13127fn apply_frame_pacing(win: &mut minifb::Window, vsync: bool) {
13128 match std::env::var("LING_FPS_CAP")
13129 .ok()
13130 .and_then(|v| v.parse::<usize>().ok())
13131 {
13132 Some(0) => win.set_target_fps(100_000),
13133 Some(cap) => win.set_target_fps(cap),
13134 None if vsync => win.set_target_fps(monitor_info().2.max(30) as usize),
13135 None => win.set_target_fps(100_000),
13136 }
13137}
13138
13139#[cfg(all(not(target_arch = "wasm32"), windows))]
13142fn monitor_info() -> (i32, i32, i32) {
13143 unsafe {
13144 extern "system" {
13145 fn GetSystemMetrics(index: i32) -> i32;
13146 fn GetDC(hwnd: isize) -> isize;
13147 fn ReleaseDC(hwnd: isize, hdc: isize) -> i32;
13148 fn GetDeviceCaps(hdc: isize, index: i32) -> i32;
13149 }
13150 let w = GetSystemMetrics(0).max(1); let h = GetSystemMetrics(1).max(1); let hdc = GetDC(0);
13153 let mut hz = if hdc != 0 { GetDeviceCaps(hdc, 116) } else { 0 }; if hdc != 0 {
13155 ReleaseDC(0, hdc);
13156 }
13157 if hz <= 1 {
13158 hz = 60; }
13160 (w, h, hz)
13161 }
13162}
13163
13164#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13168fn monitor_info() -> (i32, i32, i32) {
13169 let (w, h) = native_screen_size();
13170 (w as i32, h as i32, linux_refresh_hz().unwrap_or(60))
13171}
13172
13173#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13178fn linux_refresh_hz() -> Option<i32> {
13179 let out = std::process::Command::new("xrandr")
13180 .arg("--current")
13181 .output()
13182 .ok()?;
13183 if !out.status.success() {
13184 return None;
13185 }
13186 parse_xrandr_max_hz(&String::from_utf8_lossy(&out.stdout))
13187}
13188
13189#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13192fn parse_xrandr_max_hz(text: &str) -> Option<i32> {
13193 text.split_whitespace()
13194 .filter(|tok| tok.contains('*'))
13195 .filter_map(|tok| {
13196 tok.trim_matches(|c: char| !c.is_ascii_digit() && c != '.')
13197 .parse::<f64>()
13198 .ok()
13199 })
13200 .map(|hz| hz.round() as i32)
13201 .filter(|&hz| (24..=1000).contains(&hz))
13202 .max()
13203}
13204
13205#[cfg(target_arch = "wasm32")]
13207fn monitor_info() -> (i32, i32, i32) {
13208 let (w, h) = crate::gfx::webgl::canvas_size();
13209 (w as i32, h as i32, 60)
13210}
13211
13212#[cfg(all(test, not(target_arch = "wasm32"), not(windows)))]
13213mod xrandr_tests {
13214 use super::parse_xrandr_max_hz;
13215
13216 #[test]
13217 fn picks_highest_active_output() {
13218 let text = "\
13219eDP-1 connected primary 1920x1080+0+0
13220 1920x1080 60.00*+ 59.94
13221DP-1 connected 2560x1440+1920+0
13222 2560x1440 144.00*+ 120.00 60.00
13223";
13224 assert_eq!(parse_xrandr_max_hz(text), Some(144));
13225 }
13226
13227 #[test]
13228 fn single_output() {
13229 let text = " 1920x1080 75.00*+ 60.00\n";
13230 assert_eq!(parse_xrandr_max_hz(text), Some(75));
13231 }
13232
13233 #[test]
13234 fn no_active_mode_returns_none() {
13235 let text = "eDP-1 disconnected\n";
13236 assert_eq!(parse_xrandr_max_hz(text), None);
13237 }
13238
13239 #[test]
13240 fn out_of_range_hz_filtered() {
13241 let text = " 1x1 5000.00*+\n";
13242 assert_eq!(parse_xrandr_max_hz(text), None);
13243 }
13244}
13245
13246#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
13249fn native_screen_size() -> (f64, f64) {
13250 (1920.0, 1080.0)
13253}
13254
13255struct LingProfileState {
13267 enabled: bool,
13268 every: u64,
13269 frames: u64,
13270 calls: std::collections::HashMap<String, (u64, u128)>, }
13272
13273thread_local! {
13274 static LING_PROFILE: std::cell::RefCell<LingProfileState> = std::cell::RefCell::new({
13275 let enabled = std::env::var("LING_PROFILE").map(|v| v != "0" && !v.is_empty()).unwrap_or(false);
13276 let every = std::env::var("LING_PROFILE_EVERY").ok()
13277 .and_then(|v| v.parse::<u64>().ok()).filter(|&n| n > 0).unwrap_or(240);
13278 if enabled {
13279 eprintln!("[ling-profile] ON — report every {every} frames (set LING_PROFILE_EVERY to change)");
13280 }
13281 LingProfileState { enabled, every, frames: 0, calls: std::collections::HashMap::new() }
13282 });
13283}
13284
13285#[inline]
13286fn ling_profile_enabled() -> bool {
13287 LING_PROFILE.with(|p| p.borrow().enabled)
13288}
13289
13290thread_local! {
13291 static LING_FPS: std::cell::RefCell<(bool, f64, u32, f64)> = std::cell::RefCell::new(
13292 (std::env::var("LING_FPS").map(|v| v != "0" && !v.is_empty()).unwrap_or(false), 0.0, 0, 0.0)
13293 );
13294}
13295
13296#[cfg(not(target_arch = "wasm32"))]
13297fn ling_fps_tick() {
13298 LING_FPS.with(|s| {
13299 let mut s = s.borrow_mut();
13300 if !s.0 {
13301 return;
13302 }
13303 let now = crate::runtime::now_secs();
13304 if s.1 > 0.0 {
13305 s.3 += now - s.1;
13306 s.2 += 1;
13307 if s.2 >= 120 {
13308 let avg = s.3 / s.2 as f64;
13309 eprintln!(
13310 "[fps] {:.1} fps ({:.2} ms/frame, wall, {} frames)",
13311 1.0 / avg,
13312 avg * 1000.0,
13313 s.2
13314 );
13315 s.2 = 0;
13316 s.3 = 0.0;
13317 }
13318 }
13319 s.1 = now;
13320 });
13321}
13322
13323thread_local! {
13328 static LING_PHASE: std::cell::RefCell<(bool, u64, [u128; 5])> = std::cell::RefCell::new(
13329 (std::env::var_os("LING_PHASE").is_some(), 0, [0; 5])
13330 );
13331}
13332
13333pub mod phase {
13335 pub const FLUSH: usize = 0;
13336 pub const TOON: usize = 1;
13337 pub const BLIT: usize = 2;
13338 pub const DISTORT: usize = 3;
13339 pub const SORT: usize = 4;
13340}
13341
13342#[cfg(not(target_arch = "wasm32"))]
13343#[inline]
13344pub fn ling_phase_add(idx: usize, nanos: u128) {
13345 LING_PHASE.with(|p| {
13346 let mut p = p.borrow_mut();
13347 if p.0 {
13348 p.2[idx] += nanos;
13349 }
13350 });
13351}
13352
13353#[cfg(not(target_arch = "wasm32"))]
13354fn ling_phase_frame() {
13355 LING_PHASE.with(|p| {
13356 let mut p = p.borrow_mut();
13357 if !p.0 {
13358 return;
13359 }
13360 p.1 += 1;
13361 if p.1 >= 120 {
13362 let f = p.1 as f64;
13363 let ms = |i: usize| p.2[i] as f64 / 1e6 / f;
13364 eprintln!(
13365 "[phase] sort={:.2} flush={:.2} toon={:.2} blit={:.2} distort={:.2} ms/frame",
13366 ms(phase::SORT),
13367 ms(phase::FLUSH),
13368 ms(phase::TOON),
13369 ms(phase::BLIT),
13370 ms(phase::DISTORT)
13371 );
13372 p.1 = 0;
13373 p.2 = [0; 5];
13374 }
13375 });
13376}
13377
13378fn ling_profile_record(name: &str, nanos: u128) {
13379 let is_frame = matches!(
13381 name,
13382 "present" | "แสดงผล" | "gfx_present" | "show" | "显" | "呈现" | "表示" | "표시"
13383 );
13384 LING_PROFILE.with(|p| {
13385 let mut p = p.borrow_mut();
13386 let e = p.calls.entry(name.to_string()).or_insert((0, 0));
13387 e.0 += 1;
13388 e.1 += nanos;
13389 if is_frame {
13390 p.frames += 1;
13391 if p.frames % p.every == 0 {
13392 ling_profile_print(&p);
13393 }
13394 }
13395 });
13396}
13397
13398fn ling_profile_print(p: &LingProfileState) {
13399 let mut rows: Vec<(&String, u64, u128)> =
13400 p.calls.iter().map(|(n, (c, ns))| (n, *c, *ns)).collect();
13401 use std::cmp::Reverse;
13402 rows.sort_by_key(|x| Reverse(x.2)); let total_ns: u128 = p.calls.values().map(|(_, ns)| *ns).sum();
13404 let total_calls: u64 = p.calls.values().map(|(c, _)| *c).sum();
13405 let fr = p.frames.max(1) as f64;
13406 eprintln!(
13407 "\n┌─ LING PROFILE ── frames={} ─ builtin calls by total inclusive time ─────────────",
13408 p.frames
13409 );
13410 eprintln!(
13411 "│ {:<24} {:>9} {:>9} {:>10} {:>9} {:>6}",
13412 "builtin", "calls", "calls/fr", "total_ms", "ms/frame", "%time"
13413 );
13414 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13415 for (name, count, ns) in rows.iter().take(30) {
13416 let ms = *ns as f64 / 1e6;
13417 let pct = if total_ns > 0 {
13418 *ns as f64 / total_ns as f64 * 100.0
13419 } else {
13420 0.0
13421 };
13422 eprintln!(
13423 "│ {:<24} {:>9} {:>9.1} {:>10.1} {:>9.3} {:>5.1}%",
13424 truncate_name(name),
13425 count,
13426 *count as f64 / fr,
13427 ms,
13428 ms / fr,
13429 pct
13430 );
13431 }
13432 eprintln!("├──────────────────────────────────────────────────────────────────────────────");
13433 eprintln!(
13434 "│ TOTAL {} builtin calls, {:.1} ms over {} frames → {:.0} calls/frame, {:.2} ms/frame in builtins",
13435 total_calls,
13436 total_ns as f64 / 1e6,
13437 p.frames,
13438 total_calls as f64 / fr,
13439 total_ns as f64 / 1e6 / fr
13440 );
13441 eprintln!("└──────────────────────────────────────────────────────────────────────────────");
13442}
13443
13444fn truncate_name(s: &str) -> String {
13447 let max = 24;
13448 if s.chars().count() <= max {
13449 s.to_string()
13450 } else {
13451 let mut t: String = s.chars().take(max - 1).collect();
13452 t.push('…');
13453 t
13454 }
13455}