From 97809674baf95621adf79db781c3c91a388460c6 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 08:55:05 -0600
Subject: [PATCH 01/32] Update threejsD.js
---
threejsD.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/threejsD.js b/threejsD.js
index 043a521..ced1928 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -642,6 +642,11 @@ Promise.resolve(load()).then(() => {
Scratch.extensions.register(new ThreeRenderer())
class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
getInfo() {
return {
id: "threeScene",
@@ -673,7 +678,7 @@ Promise.resolve(load()).then(() => {
scene.name = args.NAME
scene.background = new THREE.Color("#222")
//scene.add(new THREE.GridHelper(16, 16)) //future helper section?
-
+ this.scenes = {...this.scenes, {scene}};
resetor(0)
}
From e4a038b0005a296b2239648206d77a9810069f8f Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 09:06:46 -0600
Subject: [PATCH 02/32] Update threejsD.js
---
threejsD.js | 6517 +++++++++++++++++++++++++++++++++++----------------
1 file changed, 4560 insertions(+), 1957 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index ced1928..447584f 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -1,88 +1,102 @@
+/* jshint esversion: 11 */
// Name: Extra 3D
// ID: threejsExtension
-// Description: Use three js inside Turbowarp! A 3D graphics library.
+// Description: Use three js inside Turbowarp! A 3D graphics library.
// By: Civero
// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
-(function (Scratch) {
+(function(Scratch) {
"use strict";
if (!Scratch.extensions.unsandboxed) {
throw new Error("Three-D extension must run unsandboxed");
}
- if (Scratch.vm.runtime.isPackaged) {alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`); return}
+ if (Scratch.vm.runtime.isPackaged) {
+ alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
+ return;
+ }
//if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
const vm = Scratch.vm;
- const runtime = vm.runtime
+ const runtime = vm.runtime;
const renderer = Scratch.renderer;
- const canvas = renderer.canvas
+ const canvas = renderer.canvas;
const Cast = Scratch.Cast;
- const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
-
- let alerts = false
- console.log("alerts are "+ (alerts ? "enabled" : "disabled"))
+ const menuIconURI =
+ "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
+
+ let alerts = false;
+ console.log("alerts are " + (alerts ? "enabled" : "disabled"));
- let isMouseDown = { left: false, middle: false, right: false }
- let prevMouse = { left: false, middle: false, right: false }
+ let isMouseDown = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+ let prevMouse = {
+ left: false,
+ middle: false,
+ right: false,
+ };
- let lastWidth = 0
- let lastHeight = 0
+ let lastWidth = 0;
+ let lastHeight = 0;
- let THREE
- let clock
- let running
- let loopId
+ let THREE;
+ let clock;
+ let running;
+ let loopId;
//Addons
- let GLTFLoader
- let gltf
- let OrbitControls
- let controls
- let BufferGeometryUtils
- let TextGeometry
- let fontLoad
+ let GLTFLoader;
+ let gltf;
+ let OrbitControls;
+ let controls;
+ let BufferGeometryUtils;
+ let TextGeometry;
+ let fontLoad;
//Physics
- let RAPIER
- let physicsWorld
-
- let threeRenderer
- let scene
- let camera
- let eulerOrder = "YXZ"
-
- let composer
- let passes = {}
- let customEffects = []
- let renderTargets = {}
-
- let materials = {}
- let geometries = {}
- let lights = {}
- let models = {}
-
- let assets = { //should i place materials, geometries; inside too?
+ let RAPIER;
+ let physicsWorld;
+
+ let threeRenderer;
+ let scene;
+ let camera;
+ let eulerOrder = "YXZ";
+
+ let composer;
+ let passes = {};
+ let customEffects = [];
+ let renderTargets = {};
+
+ let materials = {};
+ let geometries = {};
+ let lights = {};
+ let models = {};
+
+ let assets = {
+ //should i place materials, geometries; inside too?
textures: {},
colors: {},
fogs: {},
curves: {},
renderTargets: {}, //not the same as the global one! this one only stores textures
- }
+ };
- let raycastResult = []
+ let raycastResult = [];
function resetor(level) {
- camera = undefined
- composer.reset()
+ camera = undefined;
+ composer.reset();
- passes = {}
- customEffects = []
- renderTargets = {}
+ passes = {};
+ customEffects = [];
+ renderTargets = {};
- materials = {}
- geometries = {}
- lights = {}
- models = {}
+ materials = {};
+ geometries = {};
+ lights = {};
+ models = {};
if (level > 0) {
assets = {
@@ -91,124 +105,140 @@
fogs: {},
curves: {},
renderTargets: {},
- }
+ };
}
- updateComposers()
+ updateComposers();
}
-
-//utility
- function vector3ToString(prop) {
- if (!prop) return "0,0,0";
- const x = (typeof(prop.x) === "number") ? prop.x : (typeof(prop._x) === "number") ? prop._x : (JSON.stringify(prop).includes("X")) ? prop : 0
- const y = (typeof(prop.y) === "number") ? prop.y : (typeof(prop.y) === "number") ? prop._y : 0
- const z = (typeof(prop.z) === "number") ? prop.z : (typeof(prop.z) === "number") ? prop.z : 0
+ //utility
+ function vector3ToString(prop) {
+ if (!prop) return "0,0,0";
+
+ const x =
+ typeof prop.x === "number" ?
+ prop.x :
+ typeof prop._x === "number" ?
+ prop._x :
+ JSON.stringify(prop).includes("X") ?
+ prop :
+ 0;
+ const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
+ const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
+
+ return [x, y, z];
+ }
- return [x, y, z]
+ //objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true);
+ if (object) {
+ removeObject(name);
+ alerts ? alert(name + " already exsisted, will replace!") : null;
}
+ content.name = name;
+ content.rotation._order = eulerOrder;
+ parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+ content.physics = false;
-//objects
- function createObject(name, content, parentName) {
- let object = getObject(name, true)
- if (object) {
- removeObject(name)
- alerts ? alert(name + " already exsisted, will replace!") : null
- }
- content.name = name
- content.rotation._order = eulerOrder
- parentName === scene.name ? object = scene : object = getObject(parentName)
- content.physics = false
+ object.add(content);
+ }
- object.add(content)
- }
- function removeObject(name) {
- let object = getObject(name)
- if (!object) return
+ function removeObject(name) {
+ let object = getObject(name);
+ if (!object) return;
- scene.remove(object)
+ scene.remove(object);
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true)
- physicsWorld.removeRigidBody(object.rigidBody, true)
- object.rigidBody = null
- object.collider = null
- }
- if (object.isLight) {
- delete(lights[name])
- }
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true);
+ physicsWorld.removeRigidBody(object.rigidBody, true);
+ object.rigidBody = null;
+ object.collider = null;
}
- function getObject(name, isNew) {
- let object = null
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null; return;}
- object = scene.getObjectByName(name)
- if (!object && !isNew) {alerts ? alert(name + " does not exist! Add it to scene"):null; return;}
- return object
+ if (object.isLight) {
+ delete lights[name];
}
+ }
-//materials
- function encodeCostume (name) {
- if (name.startsWith("data:image/")) return name
- return Scratch.vm.editingTarget.sprite.costumes.find(c => c.name === name).asset.encodeDataURI()
+ function getObject(name, isNew) {
+ let object = null;
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
+ return;
+ }
+ object = scene.getObjectByName(name);
+ if (!object && !isNew) {
+ alerts ? alert(name + " does not exist! Add it to scene") : null;
+ return;
}
- function setTexutre (texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace
+ return object;
+ }
+
+ //materials
+ function encodeCostume(name) {
+ if (name.startsWith("data:image/")) return name;
+ return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ }
+
+ function setTexutre(texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace;
- if (mode === "Pixelate") {
+ if (mode === "Pixelate") {
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
- } else { //Blur
- texture.minFilter = THREE.NearestMipmapLinearFilter
- texture.magFilter = THREE.NearestMipmapLinearFilter
- }
-
- if (style === "Repeat") {
- texture.wrapS = THREE.RepeatWrapping
- texture.wrapT = THREE.RepeatWrapping
- texture.repeat.set(x, y)
- }
+ } else {
+ //Blur
+ texture.minFilter = THREE.NearestMipmapLinearFilter;
+ texture.magFilter = THREE.NearestMipmapLinearFilter;
+ }
- texture.generateMipmaps = true;
+ if (style === "Repeat") {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(x, y);
}
- async function resizeImageToSquare(uri, size = 256) {
+
+ texture.generateMipmaps = true;
+ }
+ async function resizeImageToSquare(uri, size = 256) {
return new Promise((resolve) => {
- const img = new Image()
- img.onload = () => {
- const canvas = document.createElement('canvas')
- canvas.width = size
- canvas.height = size
- const ctx = canvas.getContext('2d')
-
- // clear + draw image scaled to fit canvas
- ctx.clearRect(0, 0, size, size)
- ctx.drawImage(img, 0, 0, size, size)
-
- resolve(canvas.toDataURL()) // return normalized Data URI
- //delete canvas?
- };
- img.src = uri
- });
-}
-//light
-function updateShadowFrustum(light, focusPos) {
- if (light.type !== "DirectionalLight") return
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = size;
+ canvas.height = size;
+ const ctx = canvas.getContext("2d");
+
+ // clear + draw image scaled to fit canvas
+ ctx.clearRect(0, 0, size, size);
+ ctx.drawImage(img, 0, 0, size, size);
+
+ resolve(canvas.toDataURL()); // return normalized Data URI
+ //delete canvas?
+ };
+ img.src = uri;
+ });
+ }
+ //light
+ function updateShadowFrustum(light, focusPos) {
+ if (light.type !== "DirectionalLight") return;
// Frustum Size - Increase this value to cover a larger area.
const d = 50;
// Update Orthographic Shadow Camera Frustum
const shadowCamera = light.shadow.camera;
-
+
// Set the width/height of the frustum
shadowCamera.left = -d;
shadowCamera.right = d;
shadowCamera.top = d;
shadowCamera.bottom = -d;
-
+
// Determine ranges
- shadowCamera.near = 0.1
- shadowCamera.far = 500
+ shadowCamera.near = 0.1;
+ shadowCamera.far = 500;
// Position the Light and its Target
light.target.position.copy(focusPos);
@@ -217,69 +247,79 @@ function updateShadowFrustum(light, focusPos) {
// Ensure matrices are updated.
light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix()
- light.shadow.needsUpdate = true;
-}
-//composer
-function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
-
- // always recreate the RenderPass to point to the current scene/camera
- passes["Render"] = new RenderPass(scene, camera);
-
- // ensure composer has a RenderPass as the first pass
- const hasRender = composer.passes.some(p => p && p.scene);
- if (!hasRender) composer.addPass(passes["Render"]);
- else {
- // if composer already has one, replace it so it references current scene/camera
- const idx = composer.passes.findIndex(p => p && p.scene);
- composer.passes[idx] = passes["Render"];
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
}
-}
-//utility
-function getMouseNDC(event) {
- // Use threeRenderer.domElement for correct offset
- const rect = threeRenderer.domElement.getBoundingClientRect();
- const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
- const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
- return [x, y];
-}
-function checkCanvasSize() {
- const { width, height } = canvas
- if (width !== lastWidth || height !== lastHeight) {
- lastWidth = width
- lastHeight = height
- resize()
+ //composer
+ function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
+
+ // always recreate the RenderPass to point to the current scene/camera
+ passes["Render"] = new RenderPass(scene, camera);
+
+ // ensure composer has a RenderPass as the first pass
+ const hasRender = composer.passes.some((p) => p && p.scene);
+ if (!hasRender) composer.addPass(passes["Render"]);
+ else {
+ // if composer already has one, replace it so it references current scene/camera
+ const idx = composer.passes.findIndex((p) => p && p.scene);
+ composer.passes[idx] = passes["Render"];
+ }
+ }
+ //utility
+ function getMouseNDC(event) {
+ // Use threeRenderer.domElement for correct offset
+ const rect = threeRenderer.domElement.getBoundingClientRect();
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ return [x, y];
+ }
+
+ function checkCanvasSize() {
+ const {
+ width,
+ height
+ } = canvas;
+ if (width !== lastWidth || height !== lastHeight) {
+ lastWidth = width;
+ lastHeight = height;
+ resize();
+ }
+ requestAnimationFrame(checkCanvasSize); //rerun next frame
}
- requestAnimationFrame(checkCanvasSize) //rerun next frame
-}
-//physics
-function computeWorldBoundingBox(mesh) {
+ //physics
+ function computeWorldBoundingBox(mesh) {
// Create a Box3 in world coordinates
const box = new THREE.Box3().setFromObject(mesh);
const size = new THREE.Vector3();
box.getSize(size);
const center = new THREE.Vector3();
box.getCenter(center);
- return { size, center };
-}
-function createCuboidCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
- const collider = RAPIER.ColliderDesc.cuboid(
- size.x / 2,
- size.y / 2,
- size.z / 2
- )
+ return {
+ size,
+ center,
+ };
+ }
+
+ function createCuboidCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
return collider;
-}
-function createBallCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
+ }
+
+ function createBallCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
// radius = 1/2 of the largest verticie
const radius = Math.max(size.x, size.y, size.z) / 2;
- const collider = RAPIER.ColliderDesc.ball(radius)
- return collider //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
-}
-function createConvexHullCollider(mesh) {
+ const collider = RAPIER.ColliderDesc.ball(radius);
+ return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
+ }
+
+ function createConvexHullCollider(mesh) {
mesh.updateWorldMatrix(true, false);
const position = mesh.geometry.attributes.position;
@@ -287,1040 +327,2192 @@ function createConvexHullCollider(mesh) {
const vertex = new THREE.Vector3();
// Matrix for scale only
- const scaleMatrix = new THREE.Matrix4().makeScale(
- mesh.scale.x,
- mesh.scale.y,
- mesh.scale.z
- );
+ const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
for (let i = 0; i < position.count; i++) {
- vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
- vertices.push(vertex.x, vertex.y, vertex.z);
+ vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
+ vertices.push(vertex.x, vertex.y, vertex.z);
}
const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
return collider;
-}
-function TriMesh(mesh) {
- // Get the positions array (from your geoPoints function)
-const positions = mesh.geometry.attributes.position.array;
-const numVertices = positions.length / 3;
-
-// Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
-const indices = Array.from({ length: numVertices }, (_, i) => i);
-
-const collider = RAPIER.ColliderDesc.trimesh(
- positions,
- new Uint32Array(indices)
-);
-
-return collider
-}
-function getModel(model, name) {
- const file = runtime.getTargetForStage().getSounds().find(c => c.name === model)
- if (!file) return
-
-return new Promise((resolve, reject) => {
- gltf.parse(
- file.asset.data.buffer,
- "",
- gltf => {
- const root = gltf.scene
- root.traverse(child => {
- if (child.isMesh) {
- child.castShadow = true
- child.receiveShadow = true
- }
- });
+ }
- const mixer = new THREE.AnimationMixer(root)
- const actions = {}
- gltf.animations.forEach(clip => {
- const act = mixer.clipAction(clip)
- act.clampWhenFinished = true
- actions[clip.name] = act
- });
+ function TriMesh(mesh) {
+ // Get the positions array (from your geoPoints function)
+ const positions = mesh.geometry.attributes.position.array;
+ const numVertices = positions.length / 3;
- models[name] = { root, mixer, actions }
- resolve(root)
+ // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
+ const indices = Array.from({
+ length: numVertices,
},
- error => {
- console.error("Error parsing GLB model:", error)
- reject(error)
- }
- )})
-}
-async function openFileExplorer(format) {
- return new Promise((resolve) => {
- const input = document.createElement("input");
- input.type = "file"
- input.accept = format
- input.multiple = false
+ (_, i) => i
+ );
+
+ const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
+
+ return collider;
+ }
+
+ function getModel(model, name) {
+ const file = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === model);
+ if (!file) return;
+
+ return new Promise((resolve, reject) => {
+ gltf.parse(
+ file.asset.data.buffer,
+ "",
+ (gltf) => {
+ const root = gltf.scene;
+ root.traverse((child) => {
+ if (child.isMesh) {
+ child.castShadow = true;
+ child.receiveShadow = true;
+ }
+ });
+
+ const mixer = new THREE.AnimationMixer(root);
+ const actions = {};
+ gltf.animations.forEach((clip) => {
+ const act = mixer.clipAction(clip);
+ act.clampWhenFinished = true;
+ actions[clip.name] = act;
+ });
+
+ models[name] = {
+ root,
+ mixer,
+ actions,
+ };
+ resolve(root);
+ },
+ (error) => {
+ console.error("Error parsing GLB model:", error);
+ reject(error);
+ }
+ );
+ });
+ }
+ async function openFileExplorer(format) {
+ return new Promise((resolve) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = format;
+ input.multiple = false;
input.onchange = () => {
- resolve(input.files)
- input.remove()
+ resolve(input.files);
+ input.remove();
};
input.click();
- })
-}
-function getMeshesUsingTexture(scene, targetTexture) {
- const meshes = []
-
- scene.traverse(object => {
- if (object.material) {
- const materials = Array.isArray(object.material) ? object.material : [object.material]
- for (const material of materials) {
- if (material.map === targetTexture) {
- meshes.push(object)
- break
- }
- }
+ });
+ }
+
+ function getMeshesUsingTexture(scene, targetTexture) {
+ const meshes = [];
+
+ scene.traverse((object) => {
+ if (object.material) {
+ const materials = Array.isArray(object.material) ? object.material : [object.material];
+ for (const material of materials) {
+ if (material.map === targetTexture) {
+ meshes.push(object);
+ break;
+ }
}
- })
-
- return meshes
-}
-function getAsset(path) {
- if (typeof(path) == "string") { //string?
- if (path.includes("/")) { //has the /?
- const value = path.split("/")
- console.log(value[0], value[1])
- return assets[value[0]][value[1]]
- }
+ }
+ });
+
+ return meshes;
}
- return JSON.parse(path) //boolean or number
-}
+ function getAsset(path) {
+ if (typeof path == "string") {
+ //string?
+ if (path.includes("/")) {
+ //has the /?
+ const value = path.split("/");
+ console.log(value[0], value[1]);
+ return assets[value[0]][value[1]];
+ }
+ }
+
+ return JSON.parse(path); //boolean or number
+ }
-let mouseNDC = [0, 0]
-//loops/init
-function stopLoop() {
- if (!running) return
- running = false
+ let mouseNDC = [0, 0];
+ //loops/init
+ function stopLoop() {
+ if (!running) return;
+ running = false;
- if (loopId) {
- cancelAnimationFrame(loopId)
- loopId = null
- if (threeRenderer) threeRenderer.clear();
+ if (loopId) {
+ cancelAnimationFrame(loopId);
+ loopId = null;
+ if (threeRenderer) threeRenderer.clear();
+ }
}
-}
-async function load() {
+ async function load() {
if (!THREE) {
-
// @ts-ignore
- THREE = await import("https://esm.sh/three@0.180.0")
+ THREE = await import("https://esm.sh/three@0.180.0");
//Addons
- GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js")
- OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js")
- BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js")
- TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js")
- const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js")
- fontLoad = new FontLoader.FontLoader()
-
- const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8")
+ GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
+ OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
+ BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
+ TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
+ const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
+ fontLoad = new FontLoader.FontLoader();
+
+ const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
const {
EffectComposer,
EffectPass,
RenderPass,
-
+
Effect,
BloomEffect,
GodRaysEffect,
DotScreenEffect,
DepthOfFieldEffect,
- BlendFunction
- } = POSTPROCESSING
- //so i can use them later as global
- window.EffectComposer = EffectComposer;
- window.EffectPass = EffectPass;
- window.RenderPass = RenderPass;
- window.Effect = Effect;
- window.BloomEffect = BloomEffect;
- window.GodRaysEffect = GodRaysEffect;
- window.DotScreenEffect = DotScreenEffect;
- window.DepthOfFieldEffect = DepthOfFieldEffect;
- window.BlendFunction = BlendFunction;
-
- RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0")
- await RAPIER.init()
-
+ BlendFunction,
+ } = POSTPROCESSING;
+ //so i can use them later as global
+ window.EffectComposer = EffectComposer;
+ window.EffectPass = EffectPass;
+ window.RenderPass = RenderPass;
+ window.Effect = Effect;
+ window.BloomEffect = BloomEffect;
+ window.GodRaysEffect = GodRaysEffect;
+ window.DotScreenEffect = DotScreenEffect;
+ window.DepthOfFieldEffect = DepthOfFieldEffect;
+ window.BlendFunction = BlendFunction;
+
+ RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
+ await RAPIER.init();
+
threeRenderer = new THREE.WebGLRenderer({
powerPreference: "high-performance",
antialias: false,
stencil: false,
- depth: true
- })
- threeRenderer.setPixelRatio(window.devicePixelRatio)
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping // HDR look (test)
+ depth: true,
+ });
+ threeRenderer.setPixelRatio(window.devicePixelRatio);
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
//threeRenderer.toneMappingExposure = 1.0 //(test)
- threeRenderer.shadowMap.enabled = true
- threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap // (optional)
- threeRenderer.domElement.style.pointerEvents = 'auto' //will disable turbowarp mouse events, but enable threejs's
+ threeRenderer.shadowMap.enabled = true;
+ threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
+ threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
+
+ gltf = new GLTFLoader.GLTFLoader();
+ clock = new THREE.Clock();
- gltf = new GLTFLoader.GLTFLoader()
- clock = new THREE.Clock()
-
// Example: create a composer
- composer = new EffectComposer(threeRenderer, {frameBufferType: THREE.HalfFloatType})
-
- renderer.addOverlay( threeRenderer.domElement, "manual" )
- renderer.addOverlay(canvas, "manual")
- renderer.setBackgroundColor(1, 1, 1, 0)
-
- resize()
-
- window.addEventListener("mousedown", e => {
- if (e.button === 0) isMouseDown.left = true
- if (e.button === 1) isMouseDown.middle = true
- if (e.button === 2) isMouseDown.right = true
- })
- window.addEventListener("mouseup", e => {
- if (e.button === 0) isMouseDown.left = false; prevMouse.left = false
- if (e.button === 1) isMouseDown.middle = false; prevMouse.middle = false
- if (e.button === 2) isMouseDown.right = false; prevMouse.right = false
- })
+ composer = new EffectComposer(threeRenderer, {
+ frameBufferType: THREE.HalfFloatType,
+ });
+
+ renderer.addOverlay(threeRenderer.domElement, "manual");
+ renderer.addOverlay(canvas, "manual");
+ renderer.setBackgroundColor(1, 1, 1, 0);
+
+ resize();
+
+ window.addEventListener("mousedown", (e) => {
+ if (e.button === 0) isMouseDown.left = true;
+ if (e.button === 1) isMouseDown.middle = true;
+ if (e.button === 2) isMouseDown.right = true;
+ });
+ window.addEventListener("mouseup", (e) => {
+ if (e.button === 0) isMouseDown.left = false;
+ prevMouse.left = false;
+ if (e.button === 1) isMouseDown.middle = false;
+ prevMouse.middle = false;
+ if (e.button === 2) isMouseDown.right = false;
+ prevMouse.right = false;
+ });
// prevent contextmenu on right click
- threeRenderer.domElement.addEventListener("contextmenu", e => e.preventDefault());
+ threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
- threeRenderer.domElement.addEventListener('mousemove', (event) => {
+ threeRenderer.domElement.addEventListener("mousemove", (event) => {
mouseNDC = getMouseNDC(event);
- })
-
- running = false
- load()
-
- startRenderLoop()
- runtime.on('PROJECT_START', () => startRenderLoop())
- runtime.on('PROJECT_STOP_ALL', () => stopLoop())
- runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
- //if (!runtime.isPackaged) checkCanvasSize() //only in editor
+ });
+
+ running = false;
+ load();
+
+ startRenderLoop();
+ runtime.on("PROJECT_START", () => startRenderLoop());
+ runtime.on("PROJECT_STOP_ALL", () => stopLoop());
+ runtime.on("STAGE_SIZE_CHANGED", () => {
+ requestAnimationFrame(() => resize());
+ });
+ //if (!runtime.isPackaged) checkCanvasSize() //only in editor
}
}
-function startRenderLoop() {
- if (running) return
- running = true
-
- const loop = () => {
- if (!running) return
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step()
-
- scene.children.forEach(obj => {
- if (!(obj.isMesh) || !(obj.physics)) return
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation())
- obj.quaternion.copy(obj.rigidBody.rotation())
- }
- })
-
- }
- if (scene && camera) {
- if (controls) controls.update()
- const delta = clock.getDelta()
- Object.values(models).forEach( model => { if (model) model.mixer.update(delta) } )
+ function startRenderLoop() {
+ if (running) return;
+ running = true;
+
+ const loop = () => {
+ if (!running) return;
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step();
+
+ scene.children.forEach((obj) => {
+ if (!obj.isMesh || !obj.physics) return;
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation());
+ obj.quaternion.copy(obj.rigidBody.rotation());
+ }
+ });
+ }
+ if (scene && camera) {
+ if (controls) controls.update();
- Object.values(lights).forEach(light => updateShadowFrustum(light, camera.position))
+ const delta = clock.getDelta();
+ Object.values(models).forEach((model) => {
+ if (model) model.mixer.update(delta);
+ });
- //update custom effects time
- customEffects.forEach(e => {
- if (e.uniforms.get('time')) {
- e.uniforms.get('time').value += delta
- }
- })
- Object.values(renderTargets).forEach(t => {
- if ( t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height
- t.camera.updateProjectionMatrix()
- }
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
+ Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
- displayMeshes.forEach(mesh => {
- mesh.visible = false
- })
+ //update custom effects time
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("time")) {
+ e.uniforms.get("time").value += delta;
+ }
+ });
+ Object.values(renderTargets).forEach((t) => {
+ if (t.camera.type == "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height;
+ t.camera.updateProjectionMatrix();
+ }
+ // get meshes using the texture associated with this target
+ const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = false;
+ });
+
+ if (t.camera.type == "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target);
+ threeRenderer.clear(true, true, true);
+ threeRenderer.render(scene, t.camera);
+ } else {
+ t.target.clear(threeRenderer);
+ t.camera.update(threeRenderer, scene); //cubeCamera
+ }
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target)
- threeRenderer.clear(true, true, true)
- threeRenderer.render(scene, t.camera)
- } else {
- t.target.clear(threeRenderer)
- t.camera.update( threeRenderer, scene ) //cubeCamera
- }
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = true;
+ });
+ });
- displayMeshes.forEach(mesh => {
- mesh.visible = true
- })
- })
+ camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
+ camera.updateProjectionMatrix();
+ threeRenderer.setRenderTarget(null);
+ composer.render(delta);
+ }
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height
- camera.updateProjectionMatrix()
- threeRenderer.setRenderTarget(null)
- composer.render(delta)
- }
+ loopId = requestAnimationFrame(loop);
+ };
- loopId = requestAnimationFrame(loop)
+ loopId = requestAnimationFrame(loop);
}
- loopId = requestAnimationFrame(loop)
-}
+ function resize() {
+ const w = canvas.width;
+ const h = canvas.height;
-function resize() {
- const w = canvas.width
- const h = canvas.height
+ threeRenderer.setSize(w, h);
+ composer.setSize(w, h);
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("resolution")) {
+ e.uniforms.get("resolution").value.set(w, h);
+ }
+ });
- threeRenderer.setSize(w, h)
- composer.setSize(w, h)
- customEffects.forEach(e => {
- if (e.uniforms.get('resolution')) {
- e.uniforms.get('resolution').value.set(w,h)
+ if (camera) {
+ camera.aspect = w / h;
+ camera.updateProjectionMatrix();
}
- })
-
- if (camera) {
- camera.aspect = w / h
- camera.updateProjectionMatrix()
-}
-}
-//wait until all packages are loaded
-Promise.resolve(load()).then(() => {
-
- class threejsExtension {
- getInfo() {
- return {
- id: "threejsExtension",
- name: "Extra 3D",
- color1: "#222222",
- color2: "#222222",
- color3: "#11cc99",
- menuIconURI,
- blockIconURI: menuIconURI,
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Show Docs", func: "openDocs"},
- {blockType: Scratch.BlockType.BUTTON, text: "Toggle Alerts", func: "alerts"},
- ],
- menus: {}
- }}
- openDocs(){
- open("https://civ3ro.github.io/extensions/Documentation/")
- }
- alerts() {alerts = !alerts; alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!")}
}
- Scratch.extensions.register(new threejsExtension())
-
- class ThreeRenderer {
- getInfo() {
- return {
- id: "threeRenderer",
- name: "Three Renderer",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setRendererRatio", blockType: Scratch.BlockType.COMMAND, text: "set Pixel Ratio to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"}}},
- {opcode: "eulerOrder", blockType: Scratch.BlockType.COMMAND, text: "set euler order to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "YXZ"}}},
- ],
- menus: {}
- }}
-
- setRendererRatio(args) {
- threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE)
- }
- eulerOrder(args) {
- eulerOrder = args.VALUE
- console.log("euler order set to", eulerOrder)
+ //wait until all packages are loaded
+ Promise.resolve(load()).then(() => {
+ class threejsExtension {
+ getInfo() {
+ return {
+ id: "threejsExtension",
+ name: "Extra 3D",
+ color1: "#222222",
+ color2: "#222222",
+ color3: "#11cc99",
+ menuIconURI,
+ blockIconURI: menuIconURI,
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Show Docs",
+ func: "openDocs",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Toggle Alerts",
+ func: "alerts",
+ },
+ ],
+ menus: {},
+ };
+ }
+ openDocs() {
+ open("https://civ3ro.github.io/extensions/Documentation/");
+ }
+ alerts() {
+ alerts = !alerts;
+ alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
+ }
}
+ Scratch.extensions.register(new threejsExtension());
- }
- Scratch.extensions.register(new ThreeRenderer())
+ class ThreeRenderer {
+ getInfo() {
+ return {
+ id: "threeRenderer",
+ name: "Three Renderer",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setRendererRatio",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Pixel Ratio to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "eulerOrder",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set euler order to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "YXZ",
+ },
+ },
+ },
+ ],
+ menus: {},
+ };
+ }
- class ThreeScene {
- constructor() {
- this.THREE = THREE;
- this.scenes = {};
+ setRendererRatio(args) {
+ threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
+ }
+ eulerOrder(args) {
+ eulerOrder = args.VALUE;
+ console.log("euler order set to", eulerOrder);
+ }
}
-
- getInfo() {
- return {
- id: "threeScene",
- name: "Three Scene",
- color1: "#4638c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newScene", blockType: Scratch.BlockType.COMMAND, text: "new Scene [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}}},
-
- {opcode: "setSceneProperty", blockType: Scratch.BlockType.COMMAND, text: "set Scene [PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "sceneProperties", defaultValue: "background"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
+ Scratch.extensions.register(new ThreeRenderer());
+
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
+ getInfo() {
+ return {
+ id: "threeScene",
+ name: "Three Scene",
+ color1: "#4638c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newScene",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new Scene [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ },
+ },
+
+ {
+ opcode: "setSceneProperty",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Scene [PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneProperties",
+ defaultValue: "background",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
"---",
- {opcode: "getSceneObjects", blockType: Scratch.BlockType.REPORTER, text: "get Scene [THING]", arguments:{THING: {type: Scratch.ArgumentType.STRING, menu: "sceneThings"}}},
- {opcode: "reset", blockType: Scratch.BlockType.COMMAND, text: "Reset Everything"}
+ {
+ opcode: "getSceneObjects",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get Scene [THING]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneThings",
+ },
+ },
+ },
+ {
+ opcode: "reset",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Reset Everything",
+ },
],
- menus: {
- sceneProperties: {acceptReporters: false, items: [
- {text: "Background", value: "background"},{text: "Background Blurriness", value: "backgroundBlurriness"},{text: "Background Intensity", value: "backgroundIntensity"},{text: "Background Rotation", value: "backgroundRotation"},
- {text: "Environment", value: "environment"},{text: "Environment Intensity", value: "environmentIntensity"},{text: "Environment Rotation", value: "environmentRotation"},{text: "Fog", value: "fog"},
- ]},
- sceneThings: {acceptReporters: false, items: ["Objects", "Materials", "Geometries","Lights","Scene Properties","Other assets"]},
-
- }
- }}
-
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME
- scene.background = new THREE.Color("#222")
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {...this.scenes, {scene}};
- resetor(0)
- }
+ menus: {
+ sceneProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Background",
+ value: "background",
+ },
+ {
+ text: "Background Blurriness",
+ value: "backgroundBlurriness",
+ },
+ {
+ text: "Background Intensity",
+ value: "backgroundIntensity",
+ },
+ {
+ text: "Background Rotation",
+ value: "backgroundRotation",
+ },
+ {
+ text: "Environment",
+ value: "environment",
+ },
+ {
+ text: "Environment Intensity",
+ value: "environmentIntensity",
+ },
+ {
+ text: "Environment Rotation",
+ value: "environmentRotation",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ ],
+ },
+ sceneThings: {
+ acceptReporters: false,
+ items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
+ },
+ },
+ };
+ }
- reset() {
- resetor(1)
- }
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME;
+ scene.background = new THREE.Color("#222");
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {
+ ...this.scenes,
+ ...scene,
+ };
+ resetor(0);
+ }
+
+ reset() {
+ resetor(1);
+ }
- async setSceneProperty(args) {
+ async setSceneProperty(args) {
const property = args.PROPERTY;
const value = getAsset(args.VALUE);
scene[property] = value;
+ }
+ getSceneObjects(args) {
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse((obj) => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ else if (args.THING === "Scene Properties") {
+ console.log(scene);
+ return "check console";
+ } else if (args.THING === "Other assets") return JSON.stringify(assets);
+
+ return JSON.stringify(names); // if objects
+ }
}
- getSceneObjects(args){
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse(obj => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- }
- else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials))
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries))
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights))
- else if (args.THING === "Scene Properties") {console.log(scene); return "check console"}
- else if (args.THING === "Other assets") return JSON.stringify(assets)
-
- return JSON.stringify(names); // if objects
- }
+ Scratch.extensions.register(new ThreeScene());
- }
- Scratch.extensions.register(new ThreeScene())
-
- class ThreeCameras {
- getInfo() {
- return {
- id: "threeCameras",
- name: "Three Cameras",
- color1: "#38c59bff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addCamera", blockType: Scratch.BlockType.COMMAND, text: "add camera [TYPE] [CAMERA] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "cameraTypes"}}},
- {opcode: "setCamera", blockType: Scratch.BlockType.COMMAND, text: "set camera [PROPERTY] of [CAMERA] to [VALUE]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "0.1", exemptFromNormalization: true}}},
- {opcode: "getCamera", blockType: Scratch.BlockType.REPORTER, text: "get camera [PROPERTY] of [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}}},
+ class ThreeCameras {
+ getInfo() {
+ return {
+ id: "threeCameras",
+ name: "Three Cameras",
+ color1: "#38c59bff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add camera [TYPE] [CAMERA] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraTypes",
+ },
+ },
+ },
+ {
+ opcode: "setCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "0.1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "getCamera",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get camera [PROPERTY] of [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ },
+ },
"---",
- {opcode: "renderSceneCamera", blockType: Scratch.BlockType.COMMAND, text: "set rendering camera to [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}}},
+ {
+ opcode: "renderSceneCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rendering camera to [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ },
+ },
"---",
- {opcode: "cubeCamera", blockType: Scratch.BlockType.COMMAND, text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "cubeCamera"}, GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
+ {
+ opcode: "cubeCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "cubeCamera",
+ },
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
"---",
- {opcode: "renderTarget", blockType: Scratch.BlockType.COMMAND, text: "set a RenderTarget: [RT] for camera [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
- {opcode: "sizeTarget", blockType: Scratch.BlockType.COMMAND, text: "set RenderTarget [RT] size to [W] [H]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, W: {type: Scratch.ArgumentType.NUMBER, defaultValue: 480}, H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 360},} },
- {opcode: "getTarget", blockType: Scratch.BlockType.REPORTER, text: "get RenderTarget: [RT] texture", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
- {opcode: "removeTarget", blockType: Scratch.BlockType.COMMAND, text: "remove RenderTarget: [RT]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
+ {
+ opcode: "renderTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set a RenderTarget: [RT] for camera [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "sizeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set RenderTarget [RT] size to [W] [H]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ W: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 480,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 360,
+ },
+ },
+ },
+ {
+ opcode: "getTarget",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get RenderTarget: [RT] texture",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "removeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove RenderTarget: [RT]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
],
- menus: {
- cameraTypes: {acceptReporters: false, items: [
- {text: "Perspective", value: "PerspectiveCamera"},
- ]},
- cameraProperties: {acceptReporters: false, items: [
- {text: "Near", value: "near"},{text: "Far", value: "far"},{text: "FOV", value: "fov"},{text: "Focus (nothing...)", value: "focus"},{text: "Zoom", value: "zoom"},
- ]},
- }
- }}
- addCamera(args) {
- let v2 = new THREE.Vector2()
- threeRenderer.getSize(v2)
- const object = new THREE.PerspectiveCamera(90, v2.x / v2.y )
- object.position.z = 3
-
- createObject(args.CAMERA, object, args.GROUP)
- }
- setCamera(args) {
- let object = getObject(args.CAMERA)
- object[args.PROPERTY] = args.VALUE
- object.updateProjectionMatrix()
- }
- getCamera(args) {
- let object = getObject(args.CAMERA)
- const value = JSON.stringify(object[args.PROPERTY])
- return value
- }
- renderSceneCamera(args) {
- let object = getObject(args.CAMERA)
- if (!object) return
- camera = object
- //reset composer, else it does not update.
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
+ menus: {
+ cameraTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Perspective",
+ value: "PerspectiveCamera",
+ }, ],
+ },
+ cameraProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Near",
+ value: "near",
+ },
+ {
+ text: "Far",
+ value: "far",
+ },
+ {
+ text: "FOV",
+ value: "fov",
+ },
+ {
+ text: "Focus (nothing...)",
+ value: "focus",
+ },
+ {
+ text: "Zoom",
+ value: "zoom",
+ },
+ ],
+ },
+ },
+ };
+ }
+ addCamera(args) {
+ let v2 = new THREE.Vector2();
+ threeRenderer.getSize(v2);
+ const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
+ object.position.z = 3;
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true } )
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera( 0.1, 500, cubeRenderTarget )
- createObject(args.CAMERA, cubeCamera, args.GROUP)
+ createObject(args.CAMERA, object, args.GROUP);
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA);
+ object[args.PROPERTY] = args.VALUE;
+ object.updateProjectionMatrix();
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA);
+ const value = JSON.stringify(object[args.PROPERTY]);
+ return value;
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA);
+ if (!object) return;
+ camera = object;
+ //reset composer, else it does not update.
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
- renderTargets[args.RT] = {target: cubeRenderTarget, camera: cubeCamera}
- assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture
- }
+ cubeCamera(args) {
+ // Create cube render target
+ const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
+ generateMipmaps: true,
+ });
+ // Create cube camera
+ const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
+ createObject(args.CAMERA, cubeCamera, args.GROUP);
- renderTarget(args) {
- let object = getObject(args.CAMERA)
- const renderTarget = new THREE.WebGLRenderTarget(
- 360,
- 360,
- {
- generateMipmaps: false
- }
- )
+ renderTargets[args.RT] = {
+ target: cubeRenderTarget,
+ camera: cubeCamera,
+ };
+ assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
+ }
- renderTargets[args.RT] = {target: renderTarget, camera: object}
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture
- }
- sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H)
- }
- getTarget(args) {
- const t = renderTargets[args.RT].target.texture
- console.log(t, renderTargets[args.RT])
- return `renderTargets/${t.uuid}`
- }
- removeTarget(args) {
- delete(assets.renderTargets[renderTargets[args.RT].target.texture.uuid])
- renderTargets[args.RT].target.dispose()
- delete(renderTargets[args.RT])
+ renderTarget(args) {
+ let object = getObject(args.CAMERA);
+ const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
+ generateMipmaps: false,
+ });
+
+ renderTargets[args.RT] = {
+ target: renderTarget,
+ camera: object,
+ };
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H);
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture;
+ console.log(t, renderTargets[args.RT]);
+ return `renderTargets/${t.uuid}`;
+ }
+ removeTarget(args) {
+ delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
+ renderTargets[args.RT].target.dispose();
+ delete renderTargets[args.RT];
+ }
}
- }
- Scratch.extensions.register(new ThreeCameras())
-
- class ThreeObjects {
- getInfo() {
- return {
- id: "threeObjects",
- name: "Three Objects",
- color1: "#38c567ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addObject", blockType: Scratch.BlockType.COMMAND, text: "add object [OBJECT3D] [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},TYPE: {type: Scratch.ArgumentType.STRING, menu: "objectTypes"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "cloneObject", blockType: Scratch.BlockType.COMMAND, text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myClone"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ Scratch.extensions.register(new ThreeCameras());
+
+ class ThreeObjects {
+ getInfo() {
+ return {
+ id: "threeObjects",
+ name: "Three Objects",
+ color1: "#38c567ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add object [OBJECT3D] [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "cloneObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myClone",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "setObject", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of object [OBJECT3D] to [NAME]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "getObject", blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of object [OBJECT3D]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}}},
- {opcode: "objectE", blockType: Scratch.BlockType.BOOLEAN, text: "is there an object [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ opcode: "setObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "getObject",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of object [OBJECT3D]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ },
+ },
+ {
+ opcode: "objectE",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there an object [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "removeObject", blockType: Scratch.BlockType.COMMAND, text: "remove object [OBJECT3D] from scene", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: " ↳ Transforms"},
- {opcode: "setObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ {
+ opcode: "removeObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove object [OBJECT3D] from scene",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: " ↳ Transforms",
+ },
+ {
+ opcode: "setObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
//{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
//{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of [OBJECT3D]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Materials"},
- {opcode: "newMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "new material [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "materialTypes", defaultValue: "MeshStandardMaterial"}}},
- {opcode: "materialE",extensions: ["colours_looks"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a material [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "removeMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "remove material [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "setMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [PROPERTY] of [NAME] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "materialProperties", defaultValue: "color"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
- {opcode: "setBlending",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] blending to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "blendModes"}}},
- {opcode: "setDepth",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] depth to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "depthModes"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Geometries"},
- {opcode: "newGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new geometry [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "geometryTypes", defaultValue: "BoxGeometry"}}},
- {opcode: "geometryE",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a geometry [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "removeGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "remove geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- "---",
- {opcode: "newGeo",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new empty geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoPoints",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] vertex points to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoUVs",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] UVs to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[UVs]"}}},
- "---",
- {opcode: "splines", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create spline [NAME] from curve [CURVE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}}},
- {opcode: "splineModel", extensions: ["colours_operators"], blockType: Scratch.BlockType.COMMAND, text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, MODEL: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}, SPACING: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
- "---",
- {blockType: Scratch.BlockType.BUTTON, text: "Convert font to JSON", func: "openConv"},
- {blockType: Scratch.BlockType.BUTTON, text: "Load JSON font file", func: "loadFont"},
- {opcode: "text", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myText"}, TEXT: {type: Scratch.ArgumentType.STRING, defaultValue: "C-369"}, FONT: {type: Scratch.ArgumentType.STRING, menu: "fonts"}, S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, D: {type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1}, CS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 6}}},
- ],
- menus: {
- objectVector3: {acceptReporters: false, items: [
- {text: "Positon", value: "position"},{text: "Rotation", value: "rotation"},{text: "Scale", value: "scale"},{text: "Facing Direction (.up)", value: "up"}
- ]},
- objectProperties: {acceptReporters: false, items: [
- {text: "Geometry", value: "geometry"},{text: "Material", value: "material"},{text: "Visible (true/false)", value: "visible"},
- ]},
- objectTypes: { acceptReporters: false, items: [
- { text: "Mesh", value: "Mesh" }, { text: "Sprite", value: "Sprite" }, { text: "Points", value: "Points" }, { text: "Line", value: "Line" }, { text: "Group", value: "Group" }
- ]},
- XYZ: {acceptReporters: false, items: [{text: "X", value: "x"},{text: "Y", value: "y"},{text: "Z", value: "z"}]},
- materialProperties: {acceptReporters: false, items: [
- "|GENERAL| <-- not a property",
- { text: "Color", value: "color" },
- { text: "Map", value: "map" },
- { text: "Opacity", value: "opacity" },
- { text: "Transparent", value: "transparent" },
- { text: "Alpha Map", value: "alphaMap" },
- { text: "Alpha Test", value: "alphaTest" },
- { text: "Depth Test", value: "depthTest" },
- { text: "Depth Write", value: "depthWrite" },
- { text: "Color Write", value: "colorWrite" },
- { text: "Side", value: "side" },
- { text: "Visible", value: "visible" },/*
- { text: "Blending", value: "blending" },
- { text: "Blend Src", value: "blendSrc" },
- { text: "Blend Dst", value: "blendDst" },
- { text: "Blend Equation", value: "blendEquation" },
- { text: "Blend Src Alpha", value: "blendSrcAlpha" },
- { text: "Blend Dst Alpha", value: "blendDstAlpha" },
- { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
- { text: "Blend Aplha", value: "blendAplha" },
- { text: "Blend Color", value: "blendColor" },
- { text: "Alpha Hash", value: "alphaHash" },
- { text: "Premultiplied Alpha", value: "premultipliedAlpha" },
-
- { text: "Tone Mapped", value: "toneMapped" },
- { text: "Fog", value: "fog" },
- { text: "Flat Shading", value: "flatShading" },
-
- "|MESH Standard / Physical| <-- not a property",
- { text: "Metalness", value: "metalness" },
- { text: "Metalness Map", value: "metalnessMap" },
- { text: "Roughness", value: "roughness" },
- { text: "Reflectivity", value: "reflectivity" },
- { text: "Roughness Map", value: "roughnessMap" },
- { text: "Emissive", value: "emissive" },
- { text: "Emissive Intensity", value: "emissiveIntensity" },
- { text: "Emissive Map", value: "emissiveMap" },
- { text: "Env Map", value: "envMap" },
- { text: "Env Map Intensity", value: "envMapIntensity" },
- { text: "Env Map Rotation", value: "envMapRotation" },
- { text: "Ior", value: "ior" },
- { text: "Refraction Ratio", value: "refractionRatio" },
- { text: "Clearcoat", value: "clearcoat" },
- { text: "Clearcoat Map", value: "clearcoatMap" },
- { text: "Clearcoat Roughness", value: "clearcoatRoughness" },
- { text: "Clearcoat Roughness Map", value: "clearcoatRoughnessMap" },
- { text: "Dispersion", value: "dispersion" },
- { text: "Sheen", value: "sheen" },
- { text: "Sheen Color", value: "sheenColor" },
- { text: "Sheen Color Map", value: "sheenColorMap" },
- { text: "Sheen Roughness", value: "sheenRoughness" },
- { text: "Sheen Roughness Map", value: "sheenRoughnessMap" },
- { text: "Specular Color", value: "specularColor" },
- { text: "Specular Color Map", value: "specularColorMap" },
- { text: "Specular Intensity", value: "specularIntensity" },
- { text: "Specular Intensity Map", value: "specularIntensityMap" },
- { text: "Transmission", value: "transmission" },
- { text: "Transmission Map", value: "transmissionMap" },
- { text: "Thickness", value: "thickness" },
- { text: "Thickness Map", value: "thicknessMap" },
- { text: "Anisotropy", value: "anisotropy" },
- { text: "Anisotropy Map", value: "anisotropyMap" },
- { text: "Anisotropy Rotation", value: "anisotropyRotation" },
- { text: "Attenuation Distance", value: "attenuationDistance" },
- { text: "Attenuation Color", value: "attenuationColor" },
- { text: "Thickness", value: "thickness" },
- { text: "Iridescence", value: "iridescence" },
- { text: "Iridescence Ior", value: "iridescenceIOR" },
- { text: "Iridescence Map", value: "iridescenceMap" },
- { text: "Iridescence Thickness Range", value: "iridescenceThicknessRange" },
-
- "|MESH Displacement / Normal / Bump| <-- not a property",
- { text: "Displacement Map", value: "displacementMap" },
- { text: "Displacement Scale", value: "displacementScale" },
- { text: "Displacement Bias", value: "displacementBias" },
- { text: "Bump Map", value: "bumpMap" },
- { text: "Bump Scale", value: "bumpScale" },
- { text: "Normal Map Type", value: "normalMapType" },
-
- "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
- { text: "Shininess", value: "shininess" },
-
- { text: "Wireframe", value: "wireframe" },
- { text: "Wireframe Linewidth", value: "wireframeLinewidth" },
- { text: "Wireframe Linecap", value: "wireframeLinecap" },
- { text: "Wireframe Linejoin", value: "wireframeLinejoin" },
-
- "|POINTS| <-- not a property",
- { text: "Size", value: "size" },
- { text: "Size Attenuation", value: "sizeAttenuation" },
-
- "|LINES| <-- not a property",
- { text: "Scale", value: "scale" },
- { text: "Dash Size", value: "dashSize" },
- { text: "Gap Size", value: "gapSize" },
-
- "|SPRITES| <-- not a property",
- { text: "Rotation", value: "rotation" }
-]},
- blendModes: {acceptReporters: false, items: [
- { text: "No Blending", value: "NoBlending" },{ text: "Normal Blending", value: "NormalBlending" },{ text: "Additive Blending", value: "AdditiveBlending" },{ text: "Subtractive Blending", value: "SubtractiveBlending" },{ text: "Multiply Blending", value: "MultiplyBlending" },{ text: "Custom Blending", value: "CustomBlending" }
- ]},
- depthModes: {acceptReporters: false, items: [
- { text: "Never Depth", value: "NeverDepth" },{ text: "Always Depth", value: "AlwaysDepth" },{ text: "Equal Depth", value: "EqualDepth" },{ text: "Less Depth", value: "LessDepth" },{ text: "Less Equal Depth", value: "LessEqualDepth" },{ text: "Greater Equal Depth", value: "GreaterEqualDepth" },{ text: "Greater Depth", value: "GreaterDepth" },{ text: "Not Equal Depth", value: "NotEqualDepth" }
- ]},
- materialTypes:{acceptReporters: false, items: [
- {text:"Mesh Basic Material",value:"MeshBasicMaterial"},{text:"Mesh Standard Material",value:"MeshStandardMaterial"},{text:"Mesh Physical Material",value:"MeshPhysicalMaterial"},{text:"Mesh Lambert Material",value:"MeshLambertMaterial"},{text:"Mesh Phong Material",value:"MeshPhongMaterial"},{text:"Mesh Depth Material",value:"MeshDepthMaterial"},{text:"Mesh Normal Material",value:"MeshNormalMaterial"},{text:"Mesh Matcap Material",value:"MeshMatcapMaterial"},{text:"Mesh Toon Material",value:"MeshToonMaterial"},{text:"Line Basic Material",value:"LineBasicMaterial"},{text:"Line Dashed Material",value:"LineDashedMaterial"},{text:"Points Material",value:"PointsMaterial"},{text:"Sprite Material",value:"SpriteMaterial"},{text:"Shadow Material",value:"ShadowMaterial"}
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- geometryTypes: {acceptReporters: false, items: [
- {text: "Box Geometry", value: "BoxGeometry"},{text: "Sphere Geometry", value: "SphereGeometry"},{text: "Cylinder Geometry", value: "CylinderGeometry"},{text: "Plane Geometry", value: "PlaneGeometry"},{text: "Circle Geometry", value: "CircleGeometry"},{text: "Torus Geometry", value: "TorusGeometry"},{text: "Torus Knot Geometry", value: "TorusKnotGeometry"},
- ]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model! (GLB Loader category)"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- fonts: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.json'))
- if (models.length < 1) return [["Load a font!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
-
- }
- }}
-
- addObject(args) {
- const object = new THREE[args.TYPE]();
-
- object.castShadow = true
- object.receiveShadow = true
-
- createObject(args.OBJECT3D, object, args.GROUP)
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D)
- const clone = object.clone(true)
- clone.name
- createObject(args.NAME, clone, args.GROUP)
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
+ {
+ opcode: "getObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of [OBJECT3D]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Materials",
+ },
+ {
+ opcode: "newMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new material [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialTypes",
+ defaultValue: "MeshStandardMaterial",
+ },
+ },
+ },
+ {
+ opcode: "materialE",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a material [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "removeMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove material [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "setMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [PROPERTY] of [NAME] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialProperties",
+ defaultValue: "color",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "setBlending",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] blending to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ },
+ },
+ },
+ {
+ opcode: "setDepth",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] depth to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "depthModes",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Geometries",
+ },
+ {
+ opcode: "newGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new geometry [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "geometryTypes",
+ defaultValue: "BoxGeometry",
+ },
+ },
+ },
+ {
+ opcode: "geometryE",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a geometry [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "removeGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "newGeo",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new empty geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoPoints",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] vertex points to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoUVs",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] UVs to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[UVs]",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "splines",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create spline [NAME] from curve [CURVE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "splineModel",
+ extensions: ["colours_operators"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ MODEL: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ SPACING: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Convert font to JSON",
+ func: "openConv",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load JSON font file",
+ func: "loadFont",
+ },
+ {
+ opcode: "text",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myText",
+ },
+ TEXT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "C-369",
+ },
+ FONT: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "fonts",
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ D: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ CS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 6,
+ },
+ },
+ },
+ ],
+ menus: {
+ objectVector3: {
+ acceptReporters: false,
+ items: [{
+ text: "Positon",
+ value: "position",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Facing Direction (.up)",
+ value: "up",
+ },
+ ],
+ },
+ objectProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Geometry",
+ value: "geometry",
+ },
+ {
+ text: "Material",
+ value: "material",
+ },
+ {
+ text: "Visible (true/false)",
+ value: "visible",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh",
+ value: "Mesh",
+ },
+ {
+ text: "Sprite",
+ value: "Sprite",
+ },
+ {
+ text: "Points",
+ value: "Points",
+ },
+ {
+ text: "Line",
+ value: "Line",
+ },
+ {
+ text: "Group",
+ value: "Group",
+ },
+ ],
+ },
+ XYZ: {
+ acceptReporters: false,
+ items: [{
+ text: "X",
+ value: "x",
+ },
+ {
+ text: "Y",
+ value: "y",
+ },
+ {
+ text: "Z",
+ value: "z",
+ },
+ ],
+ },
+ materialProperties: {
+ acceptReporters: false,
+ items: [
+ "|GENERAL| <-- not a property",
+ {
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map",
+ value: "map",
+ },
+ {
+ text: "Opacity",
+ value: "opacity",
+ },
+ {
+ text: "Transparent",
+ value: "transparent",
+ },
+ {
+ text: "Alpha Map",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test",
+ value: "alphaTest",
+ },
+ {
+ text: "Depth Test",
+ value: "depthTest",
+ },
+ {
+ text: "Depth Write",
+ value: "depthWrite",
+ },
+ {
+ text: "Color Write",
+ value: "colorWrite",
+ },
+ {
+ text: "Side",
+ value: "side",
+ },
+ {
+ text: "Visible",
+ value: "visible",
+ },
+ /*
+ { text: "Blending", value: "blending" },
+ { text: "Blend Src", value: "blendSrc" },
+ { text: "Blend Dst", value: "blendDst" },
+ { text: "Blend Equation", value: "blendEquation" },
+ { text: "Blend Src Alpha", value: "blendSrcAlpha" },
+ { text: "Blend Dst Alpha", value: "blendDstAlpha" },
+ { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
+ {
+ text: "Blend Aplha",
+ value: "blendAplha",
+ },
+ {
+ text: "Blend Color",
+ value: "blendColor",
+ },
+ {
+ text: "Alpha Hash",
+ value: "alphaHash",
+ },
+ {
+ text: "Premultiplied Alpha",
+ value: "premultipliedAlpha",
+ },
+
+ {
+ text: "Tone Mapped",
+ value: "toneMapped",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ {
+ text: "Flat Shading",
+ value: "flatShading",
+ },
+
+ "|MESH Standard / Physical| <-- not a property",
+ {
+ text: "Metalness",
+ value: "metalness",
+ },
+ {
+ text: "Metalness Map",
+ value: "metalnessMap",
+ },
+ {
+ text: "Roughness",
+ value: "roughness",
+ },
+ {
+ text: "Reflectivity",
+ value: "reflectivity",
+ },
+ {
+ text: "Roughness Map",
+ value: "roughnessMap",
+ },
+ {
+ text: "Emissive",
+ value: "emissive",
+ },
+ {
+ text: "Emissive Intensity",
+ value: "emissiveIntensity",
+ },
+ {
+ text: "Emissive Map",
+ value: "emissiveMap",
+ },
+ {
+ text: "Env Map",
+ value: "envMap",
+ },
+ {
+ text: "Env Map Intensity",
+ value: "envMapIntensity",
+ },
+ {
+ text: "Env Map Rotation",
+ value: "envMapRotation",
+ },
+ {
+ text: "Ior",
+ value: "ior",
+ },
+ {
+ text: "Refraction Ratio",
+ value: "refractionRatio",
+ },
+ {
+ text: "Clearcoat",
+ value: "clearcoat",
+ },
+ {
+ text: "Clearcoat Map",
+ value: "clearcoatMap",
+ },
+ {
+ text: "Clearcoat Roughness",
+ value: "clearcoatRoughness",
+ },
+ {
+ text: "Clearcoat Roughness Map",
+ value: "clearcoatRoughnessMap",
+ },
+ {
+ text: "Dispersion",
+ value: "dispersion",
+ },
+ {
+ text: "Sheen",
+ value: "sheen",
+ },
+ {
+ text: "Sheen Color",
+ value: "sheenColor",
+ },
+ {
+ text: "Sheen Color Map",
+ value: "sheenColorMap",
+ },
+ {
+ text: "Sheen Roughness",
+ value: "sheenRoughness",
+ },
+ {
+ text: "Sheen Roughness Map",
+ value: "sheenRoughnessMap",
+ },
+ {
+ text: "Specular Color",
+ value: "specularColor",
+ },
+ {
+ text: "Specular Color Map",
+ value: "specularColorMap",
+ },
+ {
+ text: "Specular Intensity",
+ value: "specularIntensity",
+ },
+ {
+ text: "Specular Intensity Map",
+ value: "specularIntensityMap",
+ },
+ {
+ text: "Transmission",
+ value: "transmission",
+ },
+ {
+ text: "Transmission Map",
+ value: "transmissionMap",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Thickness Map",
+ value: "thicknessMap",
+ },
+ {
+ text: "Anisotropy",
+ value: "anisotropy",
+ },
+ {
+ text: "Anisotropy Map",
+ value: "anisotropyMap",
+ },
+ {
+ text: "Anisotropy Rotation",
+ value: "anisotropyRotation",
+ },
+ {
+ text: "Attenuation Distance",
+ value: "attenuationDistance",
+ },
+ {
+ text: "Attenuation Color",
+ value: "attenuationColor",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Iridescence",
+ value: "iridescence",
+ },
+ {
+ text: "Iridescence Ior",
+ value: "iridescenceIOR",
+ },
+ {
+ text: "Iridescence Map",
+ value: "iridescenceMap",
+ },
+ {
+ text: "Iridescence Thickness Range",
+ value: "iridescenceThicknessRange",
+ },
+
+ "|MESH Displacement / Normal / Bump| <-- not a property",
+ {
+ text: "Displacement Map",
+ value: "displacementMap",
+ },
+ {
+ text: "Displacement Scale",
+ value: "displacementScale",
+ },
+ {
+ text: "Displacement Bias",
+ value: "displacementBias",
+ },
+ {
+ text: "Bump Map",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ {
+ text: "Normal Map Type",
+ value: "normalMapType",
+ },
+
+ "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
+ {
+ text: "Shininess",
+ value: "shininess",
+ },
+
+ {
+ text: "Wireframe",
+ value: "wireframe",
+ },
+ {
+ text: "Wireframe Linewidth",
+ value: "wireframeLinewidth",
+ },
+ {
+ text: "Wireframe Linecap",
+ value: "wireframeLinecap",
+ },
+ {
+ text: "Wireframe Linejoin",
+ value: "wireframeLinejoin",
+ },
+
+ "|POINTS| <-- not a property",
+ {
+ text: "Size",
+ value: "size",
+ },
+ {
+ text: "Size Attenuation",
+ value: "sizeAttenuation",
+ },
+
+ "|LINES| <-- not a property",
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Dash Size",
+ value: "dashSize",
+ },
+ {
+ text: "Gap Size",
+ value: "gapSize",
+ },
+
+ "|SPRITES| <-- not a property",
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [{
+ text: "No Blending",
+ value: "NoBlending",
+ },
+ {
+ text: "Normal Blending",
+ value: "NormalBlending",
+ },
+ {
+ text: "Additive Blending",
+ value: "AdditiveBlending",
+ },
+ {
+ text: "Subtractive Blending",
+ value: "SubtractiveBlending",
+ },
+ {
+ text: "Multiply Blending",
+ value: "MultiplyBlending",
+ },
+ {
+ text: "Custom Blending",
+ value: "CustomBlending",
+ },
+ ],
+ },
+ depthModes: {
+ acceptReporters: false,
+ items: [{
+ text: "Never Depth",
+ value: "NeverDepth",
+ },
+ {
+ text: "Always Depth",
+ value: "AlwaysDepth",
+ },
+ {
+ text: "Equal Depth",
+ value: "EqualDepth",
+ },
+ {
+ text: "Less Depth",
+ value: "LessDepth",
+ },
+ {
+ text: "Less Equal Depth",
+ value: "LessEqualDepth",
+ },
+ {
+ text: "Greater Equal Depth",
+ value: "GreaterEqualDepth",
+ },
+ {
+ text: "Greater Depth",
+ value: "GreaterDepth",
+ },
+ {
+ text: "Not Equal Depth",
+ value: "NotEqualDepth",
+ },
+ ],
+ },
+ materialTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh Basic Material",
+ value: "MeshBasicMaterial",
+ },
+ {
+ text: "Mesh Standard Material",
+ value: "MeshStandardMaterial",
+ },
+ {
+ text: "Mesh Physical Material",
+ value: "MeshPhysicalMaterial",
+ },
+ {
+ text: "Mesh Lambert Material",
+ value: "MeshLambertMaterial",
+ },
+ {
+ text: "Mesh Phong Material",
+ value: "MeshPhongMaterial",
+ },
+ {
+ text: "Mesh Depth Material",
+ value: "MeshDepthMaterial",
+ },
+ {
+ text: "Mesh Normal Material",
+ value: "MeshNormalMaterial",
+ },
+ {
+ text: "Mesh Matcap Material",
+ value: "MeshMatcapMaterial",
+ },
+ {
+ text: "Mesh Toon Material",
+ value: "MeshToonMaterial",
+ },
+ {
+ text: "Line Basic Material",
+ value: "LineBasicMaterial",
+ },
+ {
+ text: "Line Dashed Material",
+ value: "LineDashedMaterial",
+ },
+ {
+ text: "Points Material",
+ value: "PointsMaterial",
+ },
+ {
+ text: "Sprite Material",
+ value: "SpriteMaterial",
+ },
+ {
+ text: "Shadow Material",
+ value: "ShadowMaterial",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ geometryTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box Geometry",
+ value: "BoxGeometry",
+ },
+ {
+ text: "Sphere Geometry",
+ value: "SphereGeometry",
+ },
+ {
+ text: "Cylinder Geometry",
+ value: "CylinderGeometry",
+ },
+ {
+ text: "Plane Geometry",
+ value: "PlaneGeometry",
+ },
+ {
+ text: "Circle Geometry",
+ value: "CircleGeometry",
+ },
+ {
+ text: "Torus Geometry",
+ value: "TorusGeometry",
+ },
+ {
+ text: "Torus Knot Geometry",
+ value: "TorusKnotGeometry",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model! (GLB Loader category)"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ fonts: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".json"));
+ if (models.length < 1) return [
+ ["Load a font!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ addObject(args) {
+ const object = new THREE[args.TYPE]();
+
+ object.castShadow = true;
+ object.receiveShadow = true;
+
+ createObject(args.OBJECT3D, object, args.GROUP);
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D);
+ const clone = object.clone(true);
+ clone.name;
+ createObject(args.NAME, clone, args.GROUP);
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ let values = JSON.parse(args.VALUE);
function degToRad(deg) {
- return deg * Math.PI / 180;
+ return (deg * Math.PI) / 180;
}
-
if (object.rigidBody) {
- const x = values[0]
- const y = values[1]
- const z = values[2]
+ const x = values[0];
+ const y = values[1];
+ const z = values[2];
if (args.PROPERTY === "rotation") {
- const euler = new THREE.Euler(
- degToRad(x),
- degToRad(y),
- degToRad(z),
- 'YXZ'
- )
- const quaternion = new THREE.Quaternion()
- quaternion.setFromEuler(euler)
+ const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
+ const quaternion = new THREE.Quaternion();
+ quaternion.setFromEuler(euler);
object.rigidBody.setRotation({
x: quaternion.x,
y: quaternion.y,
z: quaternion.z,
- w: quaternion.w
+ w: quaternion.w,
});
} else if (args.PROPERTY === "position") {
- object.rigidBody.setTranslation({ x: x, y: y, z: z }, true)
+ object.rigidBody.setTranslation({
+ x: x,
+ y: y,
+ z: z,
+ },
+ true
+ );
}
- return
+ return;
}
- if (object.isCamera == true && controls) {
-
- }
+ if (object.isCamera == true && controls) {}
if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.set(0,0,0)
+ values = values.map((v) => (v * Math.PI) / 180);
+ object.rotation.set(0, 0, 0);
+ }
+ if (object.isDirectionalLight == true) {
+ object.pos = new THREE.Vector3(...values);
+ console.log(true, values, object.pos);
+ return;
}
- if (object.isDirectionalLight == true) {object.pos = new THREE.Vector3(...values); console.log(true, values, object.pos); return}
- object[args.PROPERTY].set(...values);
+ object[args.PROPERTY].set(...values);
- if (object.type == "CubeCamera") object.updateCoordinateSystem()
- }
- /*
- changeObjectV3(args) {
- getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
+ if (object.type == "CubeCamera") object.updateCoordinateSystem();
+ }
+ /*
+ changeObjectV3(args) {
+ getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.x += values[0]
- object.rotation.y += values[1]
- object.rotation.z += values[2]
- }
- else {
- object[args.PROPERTY].add(...values);
- }
- }
- changeObjectXV3(args) {
- getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "rotation") value = value * Math.PI / 180
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.x += values[0]
+ object.rotation.y += values[1]
+ object.rotation.z += values[2]
+ }
+ else {
+ object[args.PROPERTY].add(...values);
+ }
+ }
+ changeObjectXV3(args) {
+ getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "rotation") value = value * Math.PI / 180
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let values = vector3ToString(object[args.PROPERTY])
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let values = vector3ToString(object[args.PROPERTY]);
if (args.PROPERTY === "rotation") {
- const toDeg = Math.PI/180
- values = [values[0]/toDeg,values[1]/toDeg,values[2]/toDeg,]
+ const toDeg = Math.PI / 180;
+ values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
}
- return JSON.stringify(values)
- }
- setObject(args){
- let object = getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "material") {const mat = materials[args.NAME]; if (mat) value = mat; else value = undefined}
- else if (args.PROPERTY === "geometry") {const geo = geometries[args.NAME]; if (geo) value = geo; else value = undefined}
- else value = !!value
-
- if (value == undefined) return //invalid geo/mat
- object[args.PROPERTY] = value
- }
- getObject(args){
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let value
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value
- }
- removeObject(args) {
- removeObject(args.OBJECT3D)
- }
- objectE(args) {
- return scene.children.map(o => o.name).includes(args.NAME)
- }
+ return JSON.stringify(values);
+ }
+ setObject(args) {
+ let object = getObject(args.OBJECT3D);
+ let value = args.VALUE;
+ if (args.PROPERTY === "material") {
+ const mat = materials[args.NAME];
+ if (mat) value = mat;
+ else value = undefined;
+ } else if (args.PROPERTY === "geometry") {
+ const geo = geometries[args.NAME];
+ if (geo) value = geo;
+ else value = undefined;
+ } else value = !!value;
+
+ if (value == undefined) return; //invalid geo/mat
+ object[args.PROPERTY] = value;
+ }
+ getObject(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let value;
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value;
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D);
+ }
+ objectE(args) {
+ return scene.children.map((o) => o.name).includes(args.NAME);
+ }
-//defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert ("material already exists! will replace...")
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
+ //defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof(args.VALUE) == "string" && args.VALUE.at(0) == "|") return
- const mat = materials[args.NAME]
-
- let value = args.VALUE
-
- if (args.VALUE == "false") value = false
-
- if (args.PROPERTY == "side") {value = (args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide)}
- else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE))
- else value = getAsset(value)
-
-
- console.log("o:", args.VALUE, typeof(args.VALUE))
- console.log("r:", value, typeof(value))
-
- mat[args.PROPERTY] = await (value) //await incase its a texture
- mat.needsUpdate = true
- }
- setBlending(args) {
- const mat = materials[args.NAME]
- mat.blending = THREE[args.VALUE]
- mat.premultipliedAlpha = true
- mat.needsUpdate = true
- }
- setDepth(args) {
- const mat = materials[args.NAME]
- mat.depthFunc = THREE[args.VALUE]
- mat.needsUpdate = true
- }
- removeMaterial(args){
- const mat = materials[args.NAME]
- mat.dispose()
- delete(materials[args.NAME])
- }
- materialE(args) {
- return materials[args.NAME] ? true : false
- }
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
+ const mat = materials[args.NAME];
- newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert ("geometry already exists! will replace...")
- const geo = new THREE[args.TYPE]()
- geo.name = args.NAME
+ let value = args.VALUE;
- geometries[args.NAME] = geo
- }
- setGeometry(args) {
- const geo = geometries[args.NAME]
- geo[args.PROPERTY] = (args.VALUE)
+ if (args.VALUE == "false") value = false;
- geo.needsUpdate = true;
- }
- removeGeometry(args){
- const geo = geometries[args.NAME]
- geo.dispose()
- delete(geometries[args.NAME])
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false
- }
+ if (args.PROPERTY == "side") {
+ value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
+ } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
+ else value = getAsset(value);
- newGeo(args) {
- const geometry = new THREE.BufferGeometry()
- geometry.name = args.NAME
- geometries[args.NAME] = geometry
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME]
- const positions = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v3 of each vertex of each triangle
+ console.log("o:", args.VALUE, typeof args.VALUE);
+ console.log("r:", value, typeof value);
- geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
- geometry.computeVertexNormals()
+ mat[args.PROPERTY] = await value; //await incase its a texture
+ mat.needsUpdate = true;
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME];
+ mat.blending = THREE[args.VALUE];
+ mat.premultipliedAlpha = true;
+ mat.needsUpdate = true;
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME];
+ mat.depthFunc = THREE[args.VALUE];
+ mat.needsUpdate = true;
+ }
+ removeMaterial(args) {
+ const mat = materials[args.NAME];
+ mat.dispose();
+ delete materials[args.NAME];
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false;
+ }
- geometry.needsUpdate = true
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME]
- const UVs = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v2 of each UV of each triangle
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ const geo = new THREE[args.TYPE]();
+ geo.name = args.NAME;
- geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(UVs), 2))
- geometry.needsUpdate = true
- }
+ geometries[args.NAME] = geo;
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo[args.PROPERTY] = args.VALUE;
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE))
- geometry.name = args.NAME
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo.dispose();
+ delete geometries[args.NAME];
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false;
+ }
- geometries[args.NAME] = geometry
- }
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry();
+ geometry.name = args.NAME;
+ geometries[args.NAME] = geometry;
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME];
+ const positions = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v3 of each vertex of each triangle
+
+ geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
+ geometry.computeVertexNormals();
- async splineModel(args) {
- const model = await getModel(args.MODEL, args.NAME)
- if (!model) return console.warn("Model not found:", args.MODEL)
+ geometry.needsUpdate = true;
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME];
+ const UVs = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v2 of each UV of each triangle
+
+ geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
+ geometry.needsUpdate = true;
+ }
+
+ splines(args) {
+ const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
+ geometry.name = args.NAME;
+
+ geometries[args.NAME] = geometry;
+ }
- const curve = getAsset(args.CURVE)
- const spacing = parseFloat(args.SPACING) || 1
- const curveLength = curve.getLength()
- const divisions = Math.floor(curveLength / spacing)
+ async splineModel(args) {
+ const model = await getModel(args.MODEL, args.NAME);
+ if (!model) return console.warn("Model not found:", args.MODEL);
- const geomList = []
- const matList = []
+ const curve = getAsset(args.CURVE);
+ const spacing = parseFloat(args.SPACING) || 1;
+ const curveLength = curve.getLength();
+ const divisions = Math.floor(curveLength / spacing);
- for (let i = 0; i <= divisions; i++) {
- const t = i / divisions
- const pos = curve.getPointAt(t)
- const tangent = curve.getTangentAt(t)
+ const geomList = [];
+ const matList = [];
- const temp = model.clone(true)
- temp.position.copy(pos)
+ for (let i = 0; i <= divisions; i++) {
+ const t = i / divisions;
+ const pos = curve.getPointAt(t);
+ const tangent = curve.getTangentAt(t);
- const up = new THREE.Vector3(0, 0, 1)
- const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize())
- temp.quaternion.copy(quat)
+ const temp = model.clone(true);
+ temp.position.copy(pos);
- temp.updateMatrixWorld(true)
+ const up = new THREE.Vector3(0, 0, 1);
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
+ temp.quaternion.copy(quat);
- temp.traverse(child => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone()
- geom.applyMatrix4(child.matrixWorld)
- geomList.push(geom)
- matList.push(child.material) //.clone() ?
+ temp.updateMatrixWorld(true);
+
+ temp.traverse((child) => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone();
+ geom.applyMatrix4(child.matrixWorld);
+ geomList.push(geom);
+ matList.push(child.material); //.clone() ?
+ }
+ });
+ }
+
+ const validGeoms = geomList.filter((g) => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
+ if (!ok) console.warn("geometry skipped:", g);
+ return ok;
+ });
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
+ merged.computeBoundingBox();
+ merged.computeBoundingSphere();
+
+ merged.name = args.NAME;
+ geometries[args.NAME] = merged;
+ matList.name = args.NAME;
+ materials[args.NAME] = matList;
}
- })
- }
- const validGeoms = geomList.filter(g => {
- const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position
- if (!ok) console.warn("geometry skipped:", g)
- return ok
- })
+ async text(args) {
+ const fontFile = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === args.FONT);
+ if (!fontFile) return;
- const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true)
- merged.computeBoundingBox()
- merged.computeBoundingSphere()
+ const json = new TextDecoder().decode(fontFile.asset.data.buffer);
+ const fontData = JSON.parse(json);
- merged.name = args.NAME
- geometries[args.NAME] = merged
- matList.name = args.NAME
- materials[args.NAME] = matList
- }
-
- async text(args) {
- const fontFile = runtime.getTargetForStage().getSounds().find(c => c.name === args.FONT)
- if (!fontFile) return
+ const font = fontLoad.parse(fontData);
- const json = new TextDecoder().decode(fontFile.asset.data.buffer)
- const fontData = JSON.parse(json)
+ const params = {
+ font: font,
+ size: JSON.parse(args.S),
+ height: JSON.parse(args.D),
+ curveSegments: JSON.parse(args.CS),
+ bevelEnabled: false,
+ };
+ const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
+ geometry.computeVertexNormals();
+ geometry.center(); // optional, recenters the text
- const font = fontLoad.parse(fontData)
+ geometry.name = args.NAME;
- const params = {font: font, size: JSON.parse(args.S), height: JSON.parse(args.D), curveSegments: JSON.parse(args.CS), bevelEnabled: false}
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params)
- geometry.computeVertexNormals()
- geometry.center() // optional, recenters the text
-
+ geometries[args.NAME] = geometry;
+ }
- geometry.name = args.NAME
+ async loadFont() {
+ openFileExplorer(".json").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
- geometries[args.NAME] = geometry
- }
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
- async loadFont() {
- openFileExplorer(".json").then(files => {
- const file = files[0]
- const reader = new FileReader()
+ // From lily's assets
+ // // Thank you PenguinMod for providing this code.
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result
-
- // From lily's assets
- // // Thank you PenguinMod for providing this code.
-
- const targetId = runtime.getTargetForStage().id //util.target.id not working!
- const assetName = Cast.toString(file.name)
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
- const buffer = arrayBuffer
+ const buffer = arrayBuffer;
- const storage = runtime.storage
+ const storage = runtime.storage;
const asset = storage.createAsset(
storage.AssetType.Sound,
storage.DataFormat.MP3,
@@ -1328,7 +2520,7 @@ Promise.resolve(load()).then(() => {
new Uint8Array(buffer),
null,
true
- )
+ );
try {
await vm.addSound(
@@ -1339,615 +2531,1386 @@ Promise.resolve(load()).then(() => {
name: assetName,
},
targetId
- )
- alert("Font loaded successfully!")
+ );
+ alert("Font loaded successfully!");
} catch (e) {
- console.error(e)
- alert("Error loading font.")
+ console.error(e);
+ alert("Error loading font.");
}
-
- // End of PenguinMod
+
+ // End of PenguinMod
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ openConv() {
+ {
+ open("https://gero3.github.io/facetype.js/");
}
+ }
+ }
+ Scratch.extensions.register(new ThreeObjects());
+
+ class ThreeLights {
+ getInfo() {
+ return {
+ id: "threeLights",
+ name: "Three Lights",
+ color1: "#c7a22aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add light [NAME] type [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightTypes",
+ },
+ },
+ },
+ {
+ opcode: "setLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set light [NAME][PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightProperties",
+ defaultValue: "intensity",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ lightTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Ambient Light",
+ value: "AmbientLight",
+ },
+ {
+ text: "Directional Light",
+ value: "DirectionalLight",
+ },
+ {
+ text: "Point Light",
+ value: "PointLight",
+ },
+ {
+ text: "Hemisphere Light",
+ value: "HemisphereLight",
+ },
+ {
+ text: "Spot Light",
+ value: "SpotLight",
+ },
+ ],
+ },
+ lightProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Intensity",
+ value: "intensity",
+ },
+ {
+ text: "Cast Shadow?",
+ value: "castShadow",
+ },
+ {
+ text: "Ground Color (HemisphereLight)",
+ value: "groundColor",
+ },
+ {
+ text: "Map (SpotLight)",
+ value: "map",
+ },
+ {
+ text: "Distance (SpotLight)",
+ value: "distance",
+ },
+ {
+ text: "Decay (SpotLight)",
+ value: "decay",
+ },
+ {
+ text: "Penumbra (SpotLight)",
+ value: "penumbra",
+ },
+ {
+ text: "Angle/Size (SpotLight)",
+ value: "angle",
+ },
+ {
+ text: "Power (SpotLight)",
+ value: "power",
+ },
+ {
+ text: "Target Position (Directional/SpotLight)",
+ value: "target",
+ },
+ ],
+ },
+ },
+ };
+ }
- reader.readAsArrayBuffer(file);
- })
- }
- openConv() {{open("https://gero3.github.io/facetype.js/")}}
+ addLight(args) {
+ const light = new THREE[args.TYPE](0xffffff, 1);
- }
- Scratch.extensions.register(new ThreeObjects())
-
- class ThreeLights {
- getInfo() {
- return {
- id: "threeLights",
- name: "Three Lights",
- color1: "#c7a22aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addLight", blockType: Scratch.BlockType.COMMAND, text: "add light [NAME] type [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "lightTypes"}}},
- {opcode: "setLight", blockType: Scratch.BlockType.COMMAND, text: "set light [NAME][PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lightProperties", defaultValue: "intensity"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1", exemptFromNormalization: true}}},
- ],
- menus: {
- lightTypes: {acceptReporters: false, items: [
- {text: "Ambient Light", value: "AmbientLight"},{text: "Directional Light", value: "DirectionalLight"},{text: "Point Light", value: "PointLight"},{text: "Hemisphere Light", value: "HemisphereLight"},{text: "Spot Light", value: "SpotLight"},
- ]},
- lightProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Intensity", value: "intensity"},{text: "Cast Shadow?", value: "castShadow"},
- {text: "Ground Color (HemisphereLight)", value: "groundColor"},
- {text: "Map (SpotLight)", value: "map"},{text: "Distance (SpotLight)", value: "distance"},{text: "Decay (SpotLight)", value: "decay"},{text: "Penumbra (SpotLight)", value: "penumbra"},{text: "Angle/Size (SpotLight)", value: "angle"},{text: "Power (SpotLight)", value: "power"},
- {text: "Target Position (Directional/SpotLight)", value: "target"},
- ]},
- }
- }}
+ createObject(args.NAME, light, args.GROUP);
+ lights[args.NAME] = light;
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
- addLight(args) {
- const light = new THREE[args.TYPE](0xffffff, 1)
-
- createObject(args.NAME, light, args.GROUP)
- lights[args.NAME] = light
- if (light.type === "AmbientLight" || "HemisphereLight") return
-
- light.castShadow = true
- if (light.type === "PointLight") return
- //Directional & Spot Light
- light.target.position.set(0, 0, 0)
- scene.add(light.target)
-
- light.pos = new THREE.Vector3(0,0,0)
-
- light.shadow.mapSize.width = 4096
- light.shadow.mapSize.height = 2048
-
- if (light.type === "SpotLight") {
- light.decay = 0
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true
- light.needsUpdate = true
- }
+ light.castShadow = true;
+ if (light.type === "PointLight") return;
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0);
+ scene.add(light.target);
- setLight(args) {
- const light = lights[args.NAME]
- if (!args.PROPERTY) return
- if (args.PROPERTY === "target") {
- light.target.position.set(...JSON.parse(args.VALUE)) //vector3
- light.target.updateMatrixWorld();
- }
- else {
- light[args.PROPERTY] = getAsset(args.VALUE)
+ light.pos = new THREE.Vector3(0, 0, 0);
+
+ light.shadow.mapSize.width = 4096;
+ light.shadow.mapSize.height = 2048;
+
+ if (light.type === "SpotLight") {
+ light.decay = 0;
+ light.shadow.camera.near = 500;
+ light.shadow.camera.far = 4000;
+ light.shadow.camera.fov = 30;
+ }
+ light.shadow.needsUpdate = true;
+ light.needsUpdate = true;
}
- light.needsUpdate = true
- if (light.type === "AmbientLight" || "HemisphereLight") return
+ setLight(args) {
+ const light = lights[args.NAME];
+ if (!args.PROPERTY) return;
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)); //vector3
+ light.target.updateMatrixWorld();
+ } else {
+ light[args.PROPERTY] = getAsset(args.VALUE);
+ }
+ light.needsUpdate = true;
+
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
}
+ Scratch.extensions.register(new ThreeLights());
- }
- Scratch.extensions.register(new ThreeLights())
-
- class ThreeUtilities {
- getInfo() {
- return {
- id: "threeUtility",
- name: "Three Utilities",
- color1: "#3875c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newVector2", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}}},
- {opcode: "newVector3", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y] [Z]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}, Z: {type: Scratch.ArgumentType.NUMBER}}},
+ class ThreeUtilities {
+ getInfo() {
+ return {
+ id: "threeUtility",
+ name: "Three Utilities",
+ color1: "#3875c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newVector2",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ {
+ opcode: "newVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y] [Z]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Z: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
"---",
- {opcode: "operateV3", blockType: Scratch.BlockType.REPORTER, text: "do [V3] [O] [V32]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, O: {type: Scratch.ArgumentType.STRING, menu: "operators"}, V32: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "moveVector3", blockType: Scratch.BlockType.REPORTER, text: "move [S] steps in vector [V3] in direction [D3]", arguments: {S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "directionTo", blockType: Scratch.BlockType.REPORTER, text: "direction from [V3] to [T3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, T3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ {
+ opcode: "operateV3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "do [V3] [O] [V32]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ O: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "operators",
+ },
+ V32: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "moveVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "move [S] steps in vector [V3] in direction [D3]",
+ arguments: {
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "directionTo",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "direction from [V3] to [T3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ T3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
"---",
- {opcode: "newColor", blockType: Scratch.BlockType.REPORTER, text: "New Color [HEX]", arguments: {HEX: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff"}}},
- {opcode: "newFog", blockType: Scratch.BlockType.REPORTER, text: "New Fog [COLOR] [NEAR] [FAR]", arguments: {COLOR: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff", exemptFromNormalization: true}, NEAR: {type: Scratch.ArgumentType.NUMBER}, FAR: {type: Scratch.ArgumentType.NUMBER, defaultValue: 10}}},
- {opcode: "newTexture", blockType: Scratch.BlockType.REPORTER, text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newCubeTexture", blockType: Scratch.BlockType.REPORTER, text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]", arguments: {"COSTUMEX0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEX1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ1": {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newEquirectangularTexture", blockType: Scratch.BlockType.REPORTER, text: "New Equirectangular Texture [COSTUME] [MODE]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"}}},
+ {
+ opcode: "newColor",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Color [HEX]",
+ arguments: {
+ HEX: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ },
+ },
+ },
+ {
+ opcode: "newFog",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Fog [COLOR] [NEAR] [FAR]",
+ arguments: {
+ COLOR: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ exemptFromNormalization: true,
+ },
+ NEAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ FAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 10,
+ },
+ },
+ },
+ {
+ opcode: "newTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newCubeTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUMEX0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEX1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newEquirectangularTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Equirectangular Texture [COSTUME] [MODE]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ },
+ },
"---",
- {opcode: "curve", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]", arguments: {TYPE: {type: Scratch.ArgumentType.STRING, menu: "curveTypes"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]"}, CLOSED: {type: Scratch.ArgumentType.STRING, defaultValue: "true"}}},
+ {
+ opcode: "curve",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
+ arguments: {
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "curveTypes",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
+ },
+ CLOSED: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ },
+ },
+ },
"---",
- {opcode:"mouseDown",extensions: ["colours_sensing"], blockType: Scratch.BlockType.BOOLEAN, text: "mouse [BUTTON] [action]?", arguments: {BUTTON: {type: Scratch.ArgumentType.STRING, menu: "mouseButtons"},action: {type: Scratch.ArgumentType.STRING, menu: "mouseAction"}}},
- {opcode: "mousePos",extensions: ["colours_sensing"], blockType: Scratch.BlockType.REPORTER, text: "mouse position", arguments: {}},
+ {
+ opcode: "mouseDown",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "mouse [BUTTON] [action]?",
+ arguments: {
+ BUTTON: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseButtons",
+ },
+ action: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseAction",
+ },
+ },
+ },
+ {
+ opcode: "mousePos",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "mouse position",
+ arguments: {},
+ },
"---",
- {opcode: "getItem",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "get item [ITEM] of [ARRAY]", arguments: {ITEM: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}, ARRAY: {type: Scratch.ArgumentType.STRING, defaultValue: `["myObject", "myLight"]`}}},
- {blockType: Scratch.BlockType.LABEL, text: "↳ Raycasting"},
- {opcode: "raycast", blockType: Scratch.BlockType.COMMAND, text: "Raycast from [V3] in direction [D3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,1]"}}},
- {opcode: "getRaycast", blockType: Scratch.BlockType.REPORTER, text: "get raycast [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "raycastProperties"}}},
-
- ],
- menus: {
- materialProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Map (texture)", value: "map"},{text: "Alpha Map (texture)", value: "alphaMap"},{text: "Alpha Test (0-1)", value: "alphaTest"},{text: "Side (front/back/double)", value: "side"},{text: "Bump Map (texture)", value: "bumpMap"},{text: "Bump Scale", value: "bumpScale"},
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- raycastProperties: {acceptReporters: false, items: [
- {text: "Intersected Object Names", value: "name"},{text: "Number of Objects", value: "number"},{text: "Intersected Objects distances", value: "distance"},
- ]},
- mouseButtons: {acceptReporters: false, items: ["left","middle","right"]},
- mouseAction: {acceptReporters: false, items: ["Down","Clicked"]},
- curveTypes: {acceptReporters: false, items: ["CatmullRomCurve3"]},
- operators: {acceptReporters: false, items: [
- "+","-","*","/","=","max","min","dot","cross","distance to","angle to","apply euler",
- ]}
+ {
+ opcode: "getItem",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get item [ITEM] of [ARRAY]",
+ arguments: {
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ ARRAY: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: `["myObject", "myLight"]`,
+ },
+ },
+ },
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Raycasting",
+ },
+ {
+ opcode: "raycast",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Raycast from [V3] in direction [D3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,1]",
+ },
+ },
+ },
+ {
+ opcode: "getRaycast",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get raycast [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "raycastProperties",
+ },
+ },
+ },
+ ],
+ menus: {
+ materialProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map (texture)",
+ value: "map",
+ },
+ {
+ text: "Alpha Map (texture)",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test (0-1)",
+ value: "alphaTest",
+ },
+ {
+ text: "Side (front/back/double)",
+ value: "side",
+ },
+ {
+ text: "Bump Map (texture)",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ raycastProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Intersected Object Names",
+ value: "name",
+ },
+ {
+ text: "Number of Objects",
+ value: "number",
+ },
+ {
+ text: "Intersected Objects distances",
+ value: "distance",
+ },
+ ],
+ },
+ mouseButtons: {
+ acceptReporters: false,
+ items: ["left", "middle", "right"],
+ },
+ mouseAction: {
+ acceptReporters: false,
+ items: ["Down", "Clicked"],
+ },
+ curveTypes: {
+ acceptReporters: false,
+ items: ["CatmullRomCurve3"],
+ },
+ operators: {
+ acceptReporters: false,
+ items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
+ },
+ },
+ };
+ }
+ mouseDown(args) {
+ if (args.action === "Down") return isMouseDown[args.BUTTON];
+ if (args.action === "Clicked") {
+ if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
+ else prevMouse[args.BUTTON] = true;
+ return true;
}
- }}
- mouseDown(args) {
- if (args.action === "Down") return isMouseDown[args.BUTTON]
- if (args.action === "Clicked") {
- if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false
- else prevMouse[args.BUTTON] = true; return true
}
- }
- mousePos(event) {
- return JSON.stringify(mouseNDC)
- }
- newVector3(args) {
- return JSON.stringify([args.X, args.Y, args.Z])
- }
- operateV3(args){
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const v32 = new THREE.Vector3(...JSON.parse(args.V32))
-
- let r
- if (args.O == "+") r = v3.add(v32)
- else if (args.O == "-") r = v3.sub(v32)
- else if (args.O == "*") r = v3.multiply(v32)
- else if (args.O == "/") r = v3.divide(v32)
- else if (args.O == "=") r = v3.equals(v32)
- else if (args.O == "max") r = v3.max(v32)
- else if (args.O == "min") r = v3.min(v32)
- else if (args.O == "dot") r = v3.dot(v32)
- else if (args.O == "cross") r = v3.cross(v32)
- else if (args.O == "distance to") r = v3.distanceTo(v32)
- else if (args.O == "angle to") r = v3.angleTo(v32)
- else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder))
-
- if (typeof(r) == "object") return JSON.stringify([r.x, r.y, r.z])
- else return JSON.stringify(r)
- }
-
- newVector2(args) {
- return JSON.stringify([args.X, args.Y])
- }
+ mousePos(event) {
+ return JSON.stringify(mouseNDC);
+ }
+ newVector3(args) {
+ return JSON.stringify([args.X, args.Y, args.Z]);
+ }
+ operateV3(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const v32 = new THREE.Vector3(...JSON.parse(args.V32));
+
+ let r;
+ if (args.O == "+") r = v3.add(v32);
+ else if (args.O == "-") r = v3.sub(v32);
+ else if (args.O == "*") r = v3.multiply(v32);
+ else if (args.O == "/") r = v3.divide(v32);
+ else if (args.O == "=") r = v3.equals(v32);
+ else if (args.O == "max") r = v3.max(v32);
+ else if (args.O == "min") r = v3.min(v32);
+ else if (args.O == "dot") r = v3.dot(v32);
+ else if (args.O == "cross") r = v3.cross(v32);
+ else if (args.O == "distance to") r = v3.distanceTo(v32);
+ else if (args.O == "angle to") r = v3.angleTo(v32);
+ else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
+
+ if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
+ else return JSON.stringify(r);
+ }
- moveVector3(args) {
- const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
- const steps = Number(args.S);
+ newVector2(args) {
+ return JSON.stringify([args.X, args.Y]);
+ }
- const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
+ moveVector3(args) {
+ const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
+ const steps = Number(args.S);
- const yaw = THREE.MathUtils.degToRad(yawInputDeg);
- const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
- const roll = THREE.MathUtils.degToRad(rollInputDeg);
+ const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
- const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
+ const yaw = THREE.MathUtils.degToRad(yawInputDeg);
+ const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
+ const roll = THREE.MathUtils.degToRad(rollInputDeg);
- const forwardVector = new THREE.Vector3(0, 0, -1);
- const direction = forwardVector.applyEuler(euler).normalize();
+ const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
- const newPos = currentPos.add(direction.multiplyScalar(steps));
- return JSON.stringify([newPos.x, newPos.y, newPos.z]);
- }
+ const forwardVector = new THREE.Vector3(0, 0, -1);
+ const direction = forwardVector.applyEuler(euler).normalize();
- directionTo(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const toV3 = new THREE.Vector3(...JSON.parse(args.T3))
+ const newPos = currentPos.add(direction.multiplyScalar(steps));
+ return JSON.stringify([newPos.x, newPos.y, newPos.z]);
+ }
- const direction = toV3.clone().sub(v3).normalize();
- // Pitch (X)
- const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x*direction.x + direction.z*direction.z));
- // Yaw (Y)
- const yaw = Math.atan2(direction.x, direction.z);
+ directionTo(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
- // Roll always 0
- return JSON.stringify([180+THREE.MathUtils.radToDeg(pitch),THREE.MathUtils.radToDeg(yaw),0])
- }
+ const direction = toV3.clone().sub(v3).normalize();
+ // Pitch (X)
+ const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
+ // Yaw (Y)
+ const yaw = Math.atan2(direction.x, direction.z);
- newColor(args) {
- const color = new THREE.Color(args.HEX)
- const uuid = crypto.randomUUID()
- assets.colors[uuid] = color
- return `colors/${uuid}`
- }
- newFog(args) {
- const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR)
- const uuid = crypto.randomUUID()
- assets.fogs[uuid] = fog
- return `fogs/${uuid}`
- }
- async newTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newCubeTexture(args) {
- const uris = [encodeCostume(args.COSTUMEX0),encodeCostume(args.COSTUMEX1), encodeCostume(args.COSTUMEY0),encodeCostume(args.COSTUMEY1), encodeCostume(args.COSTUMEZ0),encodeCostume(args.COSTUMEZ1)]
- const normalized = await Promise.all(uris.map(uri => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
- texture.mapping = THREE.EquirectangularReflectionMapping
-
- setTexutre(texture, args.MODE)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
+ // Roll always 0
+ return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
+ }
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g)
- if (!matches) return []
-
- return matches.map(str => {
- const nums = str
- .replace(/[\[\]\s]/g, '')
- .split(',')
- .map(Number)
- return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0)
- })
- }
- const points = parsePoints(args.POINTS)
- const curve = new THREE[args.TYPE](points)
- curve.closed = JSON.parse(args.CLOSED)
-
- const uuid = crypto.randomUUID()
- assets.curves[uuid] = curve
- return `curves/${uuid}`
- }
+ newColor(args) {
+ const color = new THREE.Color(args.HEX);
+ const uuid = crypto.randomUUID();
+ assets.colors[uuid] = color;
+ return `colors/${uuid}`;
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
+ const uuid = crypto.randomUUID();
+ assets.fogs[uuid] = fog;
+ return `fogs/${uuid}`;
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newCubeTexture(args) {
+ const uris = [
+ encodeCostume(args.COSTUMEX0),
+ encodeCostume(args.COSTUMEX1),
+ encodeCostume(args.COSTUMEY0),
+ encodeCostume(args.COSTUMEY1),
+ encodeCostume(args.COSTUMEZ0),
+ encodeCostume(args.COSTUMEZ1),
+ ];
+ const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
+
+ texture.name = "CubeTexture" + args.COSTUMEX0;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+ texture.mapping = THREE.EquirectangularReflectionMapping;
+
+ setTexutre(texture, args.MODE);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
- getItem(args) {
- const items = JSON.parse(args.ARRAY)
- const item = items[args.ITEM - 1]
- if (!item) return "0"
- return item
- }
+ curve(args) {
+ function parsePoints(input) {
+ // Match all [x,y,z] groups
+ const matches = input.match(/\[([^\]]+)\]/g);
+ if (!matches) return [];
+
+ return matches.map((str) => {
+ const nums = str
+ .replace(/[\[\]\s]/g, "")
+ .split(",")
+ .map(Number);
+ return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
+ });
+ }
+ const points = parsePoints(args.POINTS);
+ const curve = new THREE[args.TYPE](points);
+ curve.closed = JSON.parse(args.CLOSED);
+
+ const uuid = crypto.randomUUID();
+ assets.curves[uuid] = curve;
+ return `curves/${uuid}`;
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY);
+ const item = items[args.ITEM - 1];
+ if (!item) return "0";
+ return item;
+ }
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3))
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3));
// rotation is in degrees => convert to radians first
- const rot = JSON.parse(args.D3).map(v => v * Math.PI / 180)
+ const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
- const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder)
- const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize()
+ const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
+ const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
- const raycaster = new THREE.Raycaster()
- //const camera = getObject(args.CAMERA)
- raycaster.set( origin, direction );
+ const raycaster = new THREE.Raycaster();
+ //const camera = getObject(args.CAMERA)
+ raycaster.set(origin, direction);
- const intersects = raycaster.intersectObjects( scene.children, true )
+ const intersects = raycaster.intersectObjects(scene.children, true);
- raycastResult = intersects
- }
- getRaycast(args) {
- if (args.PROPERTY === "number") return raycastResult.length
- if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map(i => i.distance))
- return JSON.stringify(raycastResult.map(i => i.object[args.PROPERTY]))
+ raycastResult = intersects;
+ }
+ getRaycast(args) {
+ if (args.PROPERTY === "number") return raycastResult.length;
+ if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
+ return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
+ }
}
+ Scratch.extensions.register(new ThreeUtilities());
- }
- Scratch.extensions.register(new ThreeUtilities())
-
- class ThreeGLB {
- getInfo() {
- return {
- id: "threeGLB",
- name: "Three GLB Loader",
- color1: "#c53838ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Load GLB File", func: "loadModelFile"},
- {opcode: "addModel", blockType: Scratch.BlockType.COMMAND, text: "add [ITEM] as [NAME] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},ITEM: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}}},
- {opcode: "getModel", blockType: Scratch.BlockType.REPORTER, text: "get object [PROPERTY] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "modelProperties"}}},
- {opcode: "playAnimation", blockType: Scratch.BlockType.COMMAND, text: "play animation [ANAME] of [NAME], [TIMES] times", arguments: {TIMES: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "pauseAnimation", blockType: Scratch.BlockType.COMMAND, text: "set [TOGGLE] animation [ANAME] of [NAME]", arguments: {TOGGLE: {type: Scratch.ArgumentType.NUMBER, menu: "pauseUn"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "stopAnimation", blockType: Scratch.BlockType.COMMAND, text: "stop animation [ANAME] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
-
- ],
- menus: {
- modelProperties: {acceptReporters: false, items: [
- {text: "Animations", value: "animations"},
- ]},
- pauseUn: {acceptReporters: true, items: [{text: "Pause", value: "true"},{text: "Unpasue", value: "false"},]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
+ class ThreeGLB {
+ getInfo() {
+ return {
+ id: "threeGLB",
+ name: "Three GLB Loader",
+ color1: "#c53838ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load GLB File",
+ func: "loadModelFile",
+ },
+ {
+ opcode: "addModel",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add [ITEM] as [NAME] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ },
+ },
+ {
+ opcode: "getModel",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get object [PROPERTY] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelProperties",
+ },
+ },
+ },
+ {
+ opcode: "playAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "play animation [ANAME] of [NAME], [TIMES] times",
+ arguments: {
+ TIMES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "pauseAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [TOGGLE] animation [ANAME] of [NAME]",
+ arguments: {
+ TOGGLE: {
+ type: Scratch.ArgumentType.NUMBER,
+ menu: "pauseUn",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "stopAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stop animation [ANAME] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ modelProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Animations",
+ value: "animations",
+ }, ],
+ },
+ pauseUn: {
+ acceptReporters: true,
+ items: [{
+ text: "Pause",
+ value: "true",
+ },
+ {
+ text: "Unpasue",
+ value: "false",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
// @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- }
- }}
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
- async loadModelFile() {
+ async loadModelFile() {
+ openFileExplorer(".glb").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
- openFileExplorer(".glb").then(files => {
- const file = files[0];
- const reader = new FileReader();
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- { // From lily's assets
+ {
+ // From lily's assets
// Thank you PenguinMod for providing this code.
- {
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
+ {
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ //const res = await Scratch.fetch(args.URL);
+ //const buffer = await res.arrayBuffer();
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Model loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading model.");
+ }
+ }
+ // End of PenguinMod
+ }
+ };
- //const res = await Scratch.fetch(args.URL);
- //const buffer = await res.arrayBuffer();
- const buffer = arrayBuffer
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ async addModel(args) {
+ const group = await getModel(args.ITEM, args.NAME);
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
+ createObject(args.NAME, group, args.GROUP);
+ }
+ getModel(args) {
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString();
+ }
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Model loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading model.");
- }
- }
- // End of PenguinMod
+ playAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) {
+ console.log("no model!");
+ return;
}
- };
- reader.readAsArrayBuffer(file);
- })
-
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME)
-
- createObject(args.NAME, group, args.GROUP)
- }
- getModel(args){
- if (!models[args.NAME]) return;
- return Object.keys(models[args.NAME].actions).toString()
- }
+ const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
+ if (!action) {
+ console.log("no action!");
+ return;
+ }
- playAnimation(args) {
- const model = models[args.NAME]
- if (!model) {console.log("no model!"); return}
+ args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
- const action = model.actions[args.ANAME] //clones of models dont have a stored actions!
- if (!action) {
- console.log("no action!")
- return
+ action.reset().play();
}
+ stopAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
- args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity)
-
- action.reset()
- .play()
- }
- stopAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.stop();
- }
- pauseAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
+ const action = model.actions[args.ANAME];
+ if (action) action.stop();
+ }
+ pauseAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
- const action = model.actions[args.ANAME];
- if (action) action.paused = args.TOGGLE
+ const action = model.actions[args.ANAME];
+ if (action) action.paused = args.TOGGLE;
+ }
}
+ Scratch.extensions.register(new ThreeGLB());
- }
- Scratch.extensions.register(new ThreeGLB())
-
- class ThreeAddons {
- getInfo() {
- return {
- id: "threeAddons",
- name: "Three Addons",
- color1: "#c538a2ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.LABEL, text: "Orbit Control"},
- {opcode: "OrbitControl", blockType: Scratch.BlockType.COMMAND, text: "set addon Orbit Control [STATE]", arguments: {STATE: {type: Scratch.ArgumentType.STRING, menu: "onoff"},}},
-
- {blockType: Scratch.BlockType.LABEL, text: "Post Processing"},
- {opcode: "resetComposer", blockType: Scratch.BlockType.COMMAND, text: "reset composer"},
- {opcode: "bloom", blockType: Scratch.BlockType.COMMAND, text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},I: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, T:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "godRays", blockType: Scratch.BlockType.COMMAND, text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}, DEC:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.95}, DENS:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},EXP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1},WEI:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.4},RES:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},SAMP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 64},}},
- {opcode: "dots", blockType: Scratch.BlockType.COMMAND, text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, A: {type: Scratch.ArgumentType.ANGLE, defaultValue: 0},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "depth", blockType: Scratch.BlockType.COMMAND, text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]", arguments: {FD: {type: Scratch.ArgumentType.NUMBER, defaultValue: (3)},FL: {type: Scratch.ArgumentType.NUMBER, defaultValue: (0.001)},BS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 4},H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 240},OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}}},
+ class ThreeAddons {
+ getInfo() {
+ return {
+ id: "threeAddons",
+ name: "Three Addons",
+ color1: "#c538a2ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.LABEL,
+ text: "Orbit Control",
+ },
+ {
+ opcode: "OrbitControl",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set addon Orbit Control [STATE]",
+ arguments: {
+ STATE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "onoff",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "Post Processing",
+ },
+ {
+ opcode: "resetComposer",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset composer",
+ },
+ {
+ opcode: "bloom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ I: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ T: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "godRays",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ DEC: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.95,
+ },
+ DENS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ EXP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ WEI: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.4,
+ },
+ RES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ SAMP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 64,
+ },
+ },
+ },
+ {
+ opcode: "dots",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ A: {
+ type: Scratch.ArgumentType.ANGLE,
+ defaultValue: 0,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "depth",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ FD: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 3,
+ },
+ FL: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.001,
+ },
+ BS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 4,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 240,
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ },
+ },
"---",
- {opcode: "custom", blockType: Scratch.BlockType.COMMAND, text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myShader"}, FRA: {type: Scratch.ArgumentType.STRING}, VER: {type: Scratch.ArgumentType.STRING}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}, OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
+ {
+ opcode: "custom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myShader",
+ },
+ FRA: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ VER: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
],
- menus: {
- onoff: {acceptReporters: true, items: [{text: "enabled", value: "1"},{text: "disabled", value: "0"},]},
- blendModes: {acceptReporters: false, items: [
- "SKIP","SET","ADD","ALPHA","AVERAGE","COLOR","COLOR_BURN","COLOR_DODGE",
- "DARKEN","DIFFERENCE","DIVIDE","DST","EXCLUSION","HARD_LIGHT","HARD_MIX",
- "HUE","INVERT","INVERT_RGB","LIGHTEN","LINEAR_BURN","LINEAR_DODGE",
- "LINEAR_LIGHT","LUMINOSITY","MULTIPLY","NEGATION","NORMAL","OVERLAY",
- "PIN_LIGHT","REFLECT","SCREEN","SRC","SATURATION","SOFT_LIGHT","SUBTRACT",
- "VIVID_LIGHT"
- ]},
- }
- }}
+ menus: {
+ onoff: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "1",
+ },
+ {
+ text: "disabled",
+ value: "0",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [
+ "SKIP",
+ "SET",
+ "ADD",
+ "ALPHA",
+ "AVERAGE",
+ "COLOR",
+ "COLOR_BURN",
+ "COLOR_DODGE",
+ "DARKEN",
+ "DIFFERENCE",
+ "DIVIDE",
+ "DST",
+ "EXCLUSION",
+ "HARD_LIGHT",
+ "HARD_MIX",
+ "HUE",
+ "INVERT",
+ "INVERT_RGB",
+ "LIGHTEN",
+ "LINEAR_BURN",
+ "LINEAR_DODGE",
+ "LINEAR_LIGHT",
+ "LUMINOSITY",
+ "MULTIPLY",
+ "NEGATION",
+ "NORMAL",
+ "OVERLAY",
+ "PIN_LIGHT",
+ "REFLECT",
+ "SCREEN",
+ "SRC",
+ "SATURATION",
+ "SOFT_LIGHT",
+ "SUBTRACT",
+ "VIVID_LIGHT",
+ ],
+ },
+ },
+ };
+ }
OrbitControl(args) {
- if (controls) controls.dispose()
+ if (controls) controls.dispose();
- console.log("creating...", OrbitControls)
+ console.log("creating...", OrbitControls);
controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
- controls.enableDamping = true
-
- controls.enabled = !!args.STATE
- console.log(controls)
- }
+ controls.enableDamping = true;
- resetComposer() {
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
+ controls.enabled = !!args.STATE;
+ console.log(controls);
+ }
- bloom(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const bloomEffect = new BloomEffect({
- intensity: args.I,
- luminanceThreshold: args.T, // ← correct key
- luminanceSmoothing: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- bloomEffect.blendMode.opacity.value = args.OP
+ resetComposer() {
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
- const pass = new EffectPass(camera, bloomEffect)
+ bloom(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ bloomEffect.blendMode.opacity.value = args.OP;
- composer.addPass(pass)
- }
+ const pass = new EffectPass(camera, bloomEffect);
- godRays(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- let object = getObject(args.NAME)
- const sun = object
-
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- })
- godRays.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, godRays)
- composer.addPass(pass)
- }
+ composer.addPass(pass);
+ }
- dots(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dot = new DotScreenEffect({
- angle: args.A,
- scale: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- dot.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, dot)
- composer.addPass(pass)
- }
+ godRays(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ let object = getObject(args.NAME);
+ const sun = object;
+
+ const godRays = new GodRaysEffect(camera, sun, {
+ resolutionScale: args.RES,
+ density: args.DENS, // ray density
+ decay: args.DEC, // fade out
+ weight: args.WEI, // brightness of rays
+ exposure: args.EXP,
+ samples: args.SAMP,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ godRays.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, godRays);
+ composer.addPass(pass);
+ }
- depth(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- })
- dofEffect.blendMode.opacity.value = args.OP
-
- const dofPass = new EffectPass(camera, dofEffect)
- composer.addPass(dofPass)
- }
+ dots(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dot.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, dot);
+ composer.addPass(pass);
+ }
- async custom(args) {
- function cleanGLSL(glslCode) {
- //delete multilines comments
- let cleanedCode = glslCode.replace(/\/\*[\s\S]*?\*\//g, ' ')
- .replace(/ /g, '\n')
- .replace(/\/\/.*$/gm, ' ')
- .replace(/; /g, ';\n')
-
- return cleanedCode;
+ depth(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dofEffect.blendMode.opacity.value = args.OP;
+
+ const dofPass = new EffectPass(camera, dofEffect);
+ composer.addPass(dofPass);
}
- let fs = cleanGLSL(`
+ async custom(args) {
+ function cleanGLSL(glslCode) {
+ //delete multilines comments
+ let cleanedCode = glslCode
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
+ .replace(/ /g, "\n")
+ .replace(/\/\/.*$/gm, " ")
+ .replace(/; /g, ";\n");
+
+ return cleanedCode;
+ }
+
+ let fs = cleanGLSL(`
${args.FRA}
- `)
- if (!args.FRA.trim()) {fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`}
- const vs = cleanGLSL(`
+ `);
+ if (!args.FRA.trim()) {
+ fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
+ }
+ const vs = cleanGLSL(`
${args.VER}
- `)
- console.log(fs)
- console.log(vs)
+ `);
+ console.log(fs);
+ console.log(vs);
- const effect = new Effect(
- "Custom",
- fs,
- {
+ const effect = new Effect("Custom", fs, {
blendFunction: BlendFunction[args.BLEND],
vertexShader: vs,
- uniforms: new Map([ //uniforms usually in shaders... open to more!
- ['time', new THREE.Uniform(0.0)],
- ['resolution', new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height))]
+ uniforms: new Map([
+ //uniforms usually in shaders... open to more!
+ ["time", new THREE.Uniform(0.0)],
+ [
+ "resolution",
+ new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
+ ],
]),
- defines: new Map([['USE_TIME', '1'], ['USE_VERTEX_TRANSFORM', '']]),
- }
- );
+ defines: new Map([
+ ["USE_TIME", "1"],
+ ["USE_VERTEX_TRANSFORM", ""],
+ ]),
+ });
- effect.blendMode.opacity.value = args.OP
+ effect.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, effect);
- composer.addPass(pass);
+ const pass = new EffectPass(camera, effect);
+ composer.addPass(pass);
- customEffects.push(effect);
+ customEffects.push(effect);
+ }
}
+ Scratch.extensions.register(new ThreeAddons());
- }
- Scratch.extensions.register(new ThreeAddons())
-
- class RapierPhysics {
+ class RapierPhysics {
getInfo() {
return {
id: "rapierPhysics",
@@ -1955,119 +3918,679 @@ Promise.resolve(load()).then(() => {
color1: "#222222",
color2: "#203024ff",
color3: "#78f07eff",
- blocks: [
- {opcode: "createWorld", blockType: Scratch.BlockType.COMMAND, text: "create world | gravity:[G]", arguments: {G: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,-9.81,0]"}}},
- {opcode: "getWorld", blockType: Scratch.BlockType.REPORTER, text: "get world [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "wProp"}}},
+ blocks: [{
+ opcode: "createWorld",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create world | gravity:[G]",
+ arguments: {
+ G: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,-9.81,0]",
+ },
+ },
+ },
+ {
+ opcode: "getWorld",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get world [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "wProp",
+ },
+ },
+ },
"---",
- {opcode: "objectPhysics", blockType: Scratch.BlockType.COMMAND, text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]", arguments: {state2: {type: Scratch.ArgumentType.STRING, menu: "state2"},state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"}, type: {type: Scratch.ArgumentType.STRING, menu: "objectTypes", defaultValue: "dynamic"}, collider: {type: Scratch.ArgumentType.STRING, menu: "colliderTypes", defaultValue: "cuboid"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},mass: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},density: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},friction: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0.5"}}},
+ {
+ opcode: "objectPhysics",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
+ arguments: {
+ state2: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state2",
+ },
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ type: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ defaultValue: "dynamic",
+ },
+ collider: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderTypes",
+ defaultValue: "cuboid",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ mass: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ density: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ friction: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0.5",
+ },
+ },
+ },
"---",
- {blockType: Scratch.BlockType.LABEL, text: "- RigidBody"},
- {opcode: "setRB", blockType: Scratch.BlockType.COMMAND, text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodySets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getRB", blockType: Scratch.BlockType.REPORTER, text: "get rigidbody [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodyProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- RigidBody",
+ },
+ {
+ opcode: "setRB",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodySets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getRB",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get rigidbody [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodyProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "lockObjectAxis", blockType: Scratch.BlockType.COMMAND, text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]", arguments: {OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lockAxes"}, X: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Y: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Z: {type: Scratch.ArgumentType.STRING, menu: "tf"}}},
+ {
+ opcode: "lockObjectAxis",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
+ arguments: {
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lockAxes",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Y: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Z: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ },
+ },
"---",
- {opcode: "addForce", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,10,0]"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "forces", defaultValue: "addForce"},SPACE: {type: Scratch.ArgumentType.STRING, menu: "spaces", defaultValue: "world"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "resetForces", blockType: Scratch.BlockType.COMMAND, text: "reset [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "resetF", defaultValue: "resetForces"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ opcode: "addForce",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,10,0]",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "forces",
+ defaultValue: "addForce",
+ },
+ SPACE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "spaces",
+ defaultValue: "world",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "resetForces",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "resetF",
+ defaultValue: "resetForces",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "enableCCD", blockType: Scratch.BlockType.COMMAND, text: "enable Continuous Collision Detection for [OBJECT] [state]", arguments: {state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "oPropS", defaultValue: "physics"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ opcode: "enableCCD",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable Continuous Collision Detection for [OBJECT] [state]",
+ arguments: {
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "oPropS",
+ defaultValue: "physics",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "fixedJoint", blockType: Scratch.BlockType.COMMAND, text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},RA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},RB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},}},
- {opcode: "sphericalJoint", blockType: Scratch.BlockType.COMMAND, text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},}},
- {opcode: "revoluteJoint", blockType: Scratch.BlockType.COMMAND, text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},X: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"},}},
+ {
+ opcode: "fixedJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ RA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ RB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "sphericalJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ },
+ },
+ {
+ opcode: "revoluteJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
"---",
- {blockType: Scratch.BlockType.LABEL, text: "- Collider"},
- {opcode: "setC", blockType: Scratch.BlockType.COMMAND, text: "set collider [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderSets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getC", blockType: Scratch.BlockType.REPORTER, text: "get collider [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- Collider",
+ },
+ {
+ opcode: "setC",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderSets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getC",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get collider [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "sensorSingle", blockType: Scratch.BlockType.BOOLEAN, text: "is sensor [SENSOR] touching [OBJECT]?", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "sensorAll", blockType: Scratch.BlockType.REPORTER, text: "objects touching sensor [SENSOR]", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}}}
+ {
+ opcode: "sensorSingle",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is sensor [SENSOR] touching [OBJECT]?",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "sensorAll",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "objects touching sensor [SENSOR]",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ },
+ },
],
menus: {
- wProp: {acceptReporters: false, items: [
- {text: "Gravity", value: "gravity"}, {text: "log to console", value: "log"}
- ]},
- tf: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true", value: "true"}]},
- lockAxes: {acceptReporters: false, items: [
- {text: "Translation", value: "setEnabledTranslations"}, {text: "Rotation", value: "setEnabledRotations"}
- ]},
- rigidBodyProperties: {acceptReporters: false, items: [
- {text: "Type", value: "bodyType"},
- {text: "Linear Velocity", value: "linvel"},
- {text: "Angular Velocity", value: "angvel"},
- {text: "Translation (position)", value: "translation"},
- {text: "Rotation (quaternion)", value: "rotation"},
- {text: "Mass", value: "mass"},
- //{text: "Center of Mass", value: "centerOfMass"},
- {text: "Linear Damping", value: "linearDamping"},
- {text: "Angular Damping", value: "angularDamping"},
- {text: "Is Sleeping?", value: "isSleeping"},
- //{text: "Can Sleep?", value: "isCanSleep"},
- {text: "Gravity Scale", value: "gravityScale"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"},
- //{text: "Sleeping", value: "sleeping"}
- ]},
- rigidBodySets: {acceptReporters: false, items: [
- //{text: "Linear Velocity", value: "setLinvel"},
- //{text: "Angular Velocity", value: "setAngvel"},
- //{text: "Mass", value: "setMass"},
- {text: "Gravity Scale", value: "setGravityScale"},
- //{text: "Can Sleep?", value: "setCanSleep"},
- //{text: "Sleeping", value: "sleeping"},
- {text: "Linear Damping", value: "setLinearDamping"},
- {text: "Angular Damping", value: "setAngularDamping"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"}
- ]},
- colliderProperties: {acceptReporters: false, items: [
- //{text: "Collider Type", value: "type"},
- {text: "Is Sensor?", value: "isSensor"},
- {text: "Friction", value: "friction"},
- {text: "Restitution", value: "restitution"},
- {text: "Density", value: "density"},
- {text: "Mass", value: "mass"},
- {text: "Position", value: "translation"},
- {text: "Rotation", value: "rotation"},
- //{text: "Area", value: "area"},
- {text: "Volume", value: "volume"},
- {text: "Collision Groups", value: "collisionGroups"},
- //{text: "Collision Mask", value: "collisionMask"},
- //{text: "Is Enabled?", value: "enabled"},
- //{text: "Contact Count", value: "contactCount"},
- //{text: "RigidBody Handle", value: "rigidBody"}
- ]},
- colliderSets: {acceptReporters: false, items: [
- {text: "Friction", value: "setFriction"},
- {text: "Restitution", value: "setRestitution"},
- {text: "Density", value: "setDensity"},
- {text: "Is Sensor?", value: "setSensor"},
- {text: "Collision Groups", value: "setCollisionGroups"},
- //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
- //{text: "Position Offset", value: "setTranslation"},
- //{text: "Rotation Offset", value: "setRotation"}
- ]},
- state: {acceptReporters: true, items: [{text: "on", value: "true"},{text: "off", value: "false"}]},
- state2: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true (must be fixed)", value: "true"}]},
- spaces: {acceptReporters: false, items: [{text: "World", value: "world"},{text: "Local", value: "local"}]},
- objectTypes: {acceptReporters: false, items: [{text: "Dynamic", value: "dynamic"},{text: "Fixed", value: "fixed"},{text: "Kinematic Position Based",value: "kinematicPositionBased"}]},
- colliderTypes: {acceptReporters: false, items: [{text: "Box, Rectangle, cuboid", value: "cuboid"},{text: "Sphere, ball", value: "ball"},{text: "Custom, complex simple shapes, convexHull", value: "convexHull"},{text:"Precision, TriMesh",value:"trimesh"}]},
- forces: {acceptReporters: false, items: [{text: "Force", value: "addForce"},{text: "Torque (rotation)", value: "addTorque"},{text: "Apply Impulse", value: "applyImpulse"},{text: "Apply Torque Impulse (rotation)", value: "applyTorqueImpulse"},{text: "Linear Velocity", value: "setLinvel"},{text: "Angular Velocity", value: "setAngvel"},]},
- resetF: {acceptReporters: false, items: [{text:"Forces", value: "resetForces"},{text:"Torques", value: "resetTorques"},]}
- }
- }
+ wProp: {
+ acceptReporters: false,
+ items: [{
+ text: "Gravity",
+ value: "gravity",
+ },
+ {
+ text: "log to console",
+ value: "log",
+ },
+ ],
+ },
+ tf: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true",
+ value: "true",
+ },
+ ],
+ },
+ lockAxes: {
+ acceptReporters: false,
+ items: [{
+ text: "Translation",
+ value: "setEnabledTranslations",
+ },
+ {
+ text: "Rotation",
+ value: "setEnabledRotations",
+ },
+ ],
+ },
+ rigidBodyProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Type",
+ value: "bodyType",
+ },
+ {
+ text: "Linear Velocity",
+ value: "linvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "angvel",
+ },
+ {
+ text: "Translation (position)",
+ value: "translation",
+ },
+ {
+ text: "Rotation (quaternion)",
+ value: "rotation",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ //{text: "Center of Mass", value: "centerOfMass"},
+ {
+ text: "Linear Damping",
+ value: "linearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "angularDamping",
+ },
+ {
+ text: "Is Sleeping?",
+ value: "isSleeping",
+ },
+ //{text: "Can Sleep?", value: "isCanSleep"},
+ {
+ text: "Gravity Scale",
+ value: "gravityScale",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ //{text: "Sleeping", value: "sleeping"}
+ ],
+ },
+ rigidBodySets: {
+ acceptReporters: false,
+ items: [
+ //{text: "Linear Velocity", value: "setLinvel"},
+ //{text: "Angular Velocity", value: "setAngvel"},
+ //{text: "Mass", value: "setMass"},
+ {
+ text: "Gravity Scale",
+ value: "setGravityScale",
+ },
+ //{text: "Can Sleep?", value: "setCanSleep"},
+ //{text: "Sleeping", value: "sleeping"},
+ {
+ text: "Linear Damping",
+ value: "setLinearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "setAngularDamping",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ ],
+ },
+ colliderProperties: {
+ acceptReporters: false,
+ items: [
+ //{text: "Collider Type", value: "type"},
+ {
+ text: "Is Sensor?",
+ value: "isSensor",
+ },
+ {
+ text: "Friction",
+ value: "friction",
+ },
+ {
+ text: "Restitution",
+ value: "restitution",
+ },
+ {
+ text: "Density",
+ value: "density",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ {
+ text: "Position",
+ value: "translation",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ //{text: "Area", value: "area"},
+ {
+ text: "Volume",
+ value: "volume",
+ },
+ {
+ text: "Collision Groups",
+ value: "collisionGroups",
+ },
+ //{text: "Collision Mask", value: "collisionMask"},
+ //{text: "Is Enabled?", value: "enabled"},
+ //{text: "Contact Count", value: "contactCount"},
+ //{text: "RigidBody Handle", value: "rigidBody"}
+ ],
+ },
+ colliderSets: {
+ acceptReporters: false,
+ items: [{
+ text: "Friction",
+ value: "setFriction",
+ },
+ {
+ text: "Restitution",
+ value: "setRestitution",
+ },
+ {
+ text: "Density",
+ value: "setDensity",
+ },
+ {
+ text: "Is Sensor?",
+ value: "setSensor",
+ },
+ {
+ text: "Collision Groups",
+ value: "setCollisionGroups",
+ },
+ //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
+ //{text: "Position Offset", value: "setTranslation"},
+ //{text: "Rotation Offset", value: "setRotation"}
+ ],
+ },
+ state: {
+ acceptReporters: true,
+ items: [{
+ text: "on",
+ value: "true",
+ },
+ {
+ text: "off",
+ value: "false",
+ },
+ ],
+ },
+ state2: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true (must be fixed)",
+ value: "true",
+ },
+ ],
+ },
+ spaces: {
+ acceptReporters: false,
+ items: [{
+ text: "World",
+ value: "world",
+ },
+ {
+ text: "Local",
+ value: "local",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Dynamic",
+ value: "dynamic",
+ },
+ {
+ text: "Fixed",
+ value: "fixed",
+ },
+ {
+ text: "Kinematic Position Based",
+ value: "kinematicPositionBased",
+ },
+ ],
+ },
+ colliderTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box, Rectangle, cuboid",
+ value: "cuboid",
+ },
+ {
+ text: "Sphere, ball",
+ value: "ball",
+ },
+ {
+ text: "Custom, complex simple shapes, convexHull",
+ value: "convexHull",
+ },
+ {
+ text: "Precision, TriMesh",
+ value: "trimesh",
+ },
+ ],
+ },
+ forces: {
+ acceptReporters: false,
+ items: [{
+ text: "Force",
+ value: "addForce",
+ },
+ {
+ text: "Torque (rotation)",
+ value: "addTorque",
+ },
+ {
+ text: "Apply Impulse",
+ value: "applyImpulse",
+ },
+ {
+ text: "Apply Torque Impulse (rotation)",
+ value: "applyTorqueImpulse",
+ },
+ {
+ text: "Linear Velocity",
+ value: "setLinvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "setAngvel",
+ },
+ ],
+ },
+ resetF: {
+ acceptReporters: false,
+ items: [{
+ text: "Forces",
+ value: "resetForces",
+ },
+ {
+ text: "Torques",
+ value: "resetTorques",
+ },
+ ],
+ },
+ },
+ };
}
joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true)
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
}
fixedJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- let RA = JSON.parse(args.RA).map(Number)
- let RB = JSON.parse(args.RB).map(Number)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ let RA = JSON.parse(args.RA).map(Number);
+ let RB = JSON.parse(args.RB).map(Number);
RA = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
@@ -2075,339 +4598,419 @@ Promise.resolve(load()).then(() => {
THREE.MathUtils.degToRad(RA[1]),
THREE.MathUtils.degToRad(RA[2])
)
- )
+ );
RB = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
THREE.MathUtils.degToRad(RB[0]),
THREE.MathUtils.degToRad(RB[1]),
THREE.MathUtils.degToRad(RB[2])
)
- )
-
- const data = RAPIER.JointData.fixed(
- { x: VA[0], y: VA[1], z: VA[2] }, RA,
- { x: VB[0], y: VB[1], z: VB[2] }, RB
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ );
+
+ const data = RAPIER.JointData.fixed({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ },
+ RA, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ },
+ RB
+ );
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
sphericalJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
-
- const data = RAPIER.JointData.spherical(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+
+ const data = RAPIER.JointData.spherical({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
revoluteJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.revolute(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.revolute({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
prismaticJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.prismatic(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.prismatic({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
createWorld(args) {
- const v3 = JSON.parse(args.G).map(Number)
- const gravity = { x: v3[0], y: v3[1], z: v3[2]}
- physicsWorld = new RAPIER.World(gravity)
+ const v3 = JSON.parse(args.G).map(Number);
+ const gravity = {
+ x: v3[0],
+ y: v3[1],
+ z: v3[2],
+ };
+ physicsWorld = new RAPIER.World(gravity);
- console.log(physicsWorld)
+ console.log(physicsWorld);
}
getWorld(args) {
- if (args.PROPERTY === "log") {console.log(physicsWorld); return "logged"}
- return JSON.stringify(physicsWorld[args.PROPERTY])
+ if (args.PROPERTY === "log") {
+ console.log(physicsWorld);
+ return "logged";
+ }
+ return JSON.stringify(physicsWorld[args.PROPERTY]);
}
setRB(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.rigidBody[args.PROPERTY](value)
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.rigidBody[args.PROPERTY](value);
}
setC(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.collider[args.PROPERTY](value)
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.collider[args.PROPERTY](value);
}
getRB(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.rigidBody[args.PROPERTY]())
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.rigidBody[args.PROPERTY]());
}
getC(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.collider[args.PROPERTY]())
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.collider[args.PROPERTY]());
}
lockObjectAxis(args) {
- let object = getObject(args.OBJECT)
- const x = !JSON.parse(args.X)
- const y = !JSON.parse(args.Y)
- const z = !JSON.parse(args.Z)
- object.rigidBody[args.PROPERTY](x,y,z,true) //changes is xyz, wake up
+ let object = getObject(args.OBJECT);
+ const x = !JSON.parse(args.X);
+ const y = !JSON.parse(args.Y);
+ const z = !JSON.parse(args.Z);
+ object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
}
objectPhysics(args) {
- let object = getObject(args.OBJECT)
- object.physics = JSON.parse(args.state)
+ let object = getObject(args.OBJECT);
+ object.physics = JSON.parse(args.state);
if (JSON.parse(args.state)) {
//if already exists delete:
if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
}
/*asing a rigidbody and collider to object and add them to physicsWorld*/
- let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
+ let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
.setTranslation(object.position.x, object.position.y, object.position.z)
- .setRotation({w: object.quaternion._w, x: object.quaternion._x, y: object.quaternion._y, z: object.quaternion._z})
-
- let colliderDesc
- switch(args.collider) {
- case "cuboid": colliderDesc = createCuboidCollider(object,); break
- case "ball": colliderDesc = createBallCollider(object); break
- case "convexHull": colliderDesc = createConvexHullCollider(object); break
- case "trimesh": colliderDesc = TriMesh(object); break
- }
- colliderDesc.setSensor(JSON.parse(args.state2)).setMass(args.mass).setDensity(args.density).setFriction(args.friction)
+ .setRotation({
+ w: object.quaternion._w,
+ x: object.quaternion._x,
+ y: object.quaternion._y,
+ z: object.quaternion._z,
+ });
+
+ let colliderDesc;
+ switch (args.collider) {
+ case "cuboid":
+ colliderDesc = createCuboidCollider(object);
+ break;
+ case "ball":
+ colliderDesc = createBallCollider(object);
+ break;
+ case "convexHull":
+ colliderDesc = createConvexHullCollider(object);
+ break;
+ case "trimesh":
+ colliderDesc = TriMesh(object);
+ break;
+ }
+ colliderDesc
+ .setSensor(JSON.parse(args.state2))
+ .setMass(args.mass)
+ .setDensity(args.density)
+ .setFriction(args.friction);
- let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc)
- let collider = physicsWorld.createCollider(colliderDesc, rigidBody)
+ let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
+ let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
- object.rigidBody = rigidBody
- object.collider = collider
+ object.rigidBody = rigidBody;
+ object.collider = collider;
} else {
/*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
}
-
}
enableCCD(args) {
- let object = getObject(args.OBJECT)
+ let object = getObject(args.OBJECT);
if (object.physics) {
- let rigidBody = object.rigidBody
- rigidBody.enableCcd(JSON.parse(args.state))
+ let rigidBody = object.rigidBody;
+ rigidBody.enableCcd(JSON.parse(args.state));
}
- }
+ }
- addForce(args) {
- let object = getObject(args.OBJECT)
- const vector = JSON.parse(args.VALUE).map(Number)
-
- let force = new THREE.Vector3(vector[0],vector[1],vector[2])
+ addForce(args) {
+ let object = getObject(args.OBJECT);
+ const vector = JSON.parse(args.VALUE).map(Number);
+
+ let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
if (args.SPACE === "local") {
force.applyQuaternion(object.quaternion);
}
- object.rigidBody[args.PROPERTY](force,true)
- }
-
- resetForces(args) {
- rigidBody[args.PROPERTY](true)
- }
+ object.rigidBody[args.PROPERTY](force, true);
+ }
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR)
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true);
+ }
- let object = getObject(args.OBJECT)
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR);
- let touching = false
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- if (otherCollider === object.collider) touching = true
- })
+ let object = getObject(args.OBJECT);
- return touching
- }
+ let touching = false;
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ if (otherCollider === object.collider) touching = true;
+ });
- sensorAll(args) {
- const sensor = getObject(args.SENSOR)
+ return touching;
+ }
- const touchedObjects = []
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR);
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- // find owner of collider
- const otherObject = scene.children.find(o => o.collider === otherCollider)
- console.log(otherCollider)
- if (otherObject) touchedObjects.push(otherObject.name)
- })
+ const touchedObjects = [];
- return JSON.stringify(touchedObjects)
- }
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ // find owner of collider
+ const otherObject = scene.children.find((o) => o.collider === otherCollider);
+ console.log(otherCollider);
+ if (otherObject) touchedObjects.push(otherObject.name);
+ });
+ return JSON.stringify(touchedObjects);
+ }
}
- Scratch.extensions.register(new RapierPhysics())
-
- //Thanks to the PointerLock extension of Turbowarp
- const mouse = vm.runtime.ioDevices.mouse;
- let isLocked = false;
- let isPointerLockEnabled = false;
-
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
+ Scratch.extensions.register(new RapierPhysics());
- const postMouseData = (e, isDown) => {
- const { movementX, movementY } = e;
- const { width, height } = rect;
- const x = mouse._clientX + movementX;
- const y = mouse._clientY - movementY;
- mouse._clientX = x;
- mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
- mouse._clientY = y;
- mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
- if (typeof isDown === "boolean") {
- const data = {
- button: e.button,
- isDown,
- };
- originalPostIOData(data);
- }
- };
+ //Thanks to the PointerLock extension of Turbowarp
+ const mouse = vm.runtime.ioDevices.mouse;
+ let isLocked = false;
+ let isPointerLockEnabled = false;
- const mouseDevice = vm.runtime.ioDevices.mouse;
- const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
- mouseDevice.postData = (data) => {
- if (!isPointerLockEnabled) {
- return originalPostIOData(data);
- }
- };
+ let rect = threeRenderer.domElement.getBoundingClientRect();
+ document.addEventListener("resize", () => {
+ rect = threeRenderer.domElement.getBoundingClientRect();
+ });
+
+ const postMouseData = (e, isDown) => {
+ const {
+ movementX,
+ movementY
+ } = e;
+ const {
+ width,
+ height
+ } = rect;
+ const x = mouse._clientX + movementX;
+ const y = mouse._clientY - movementY;
+ mouse._clientX = x;
+ mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
+ mouse._clientY = y;
+ mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
+ if (typeof isDown === "boolean") {
+ const data = {
+ button: e.button,
+ isDown,
+ };
+ originalPostIOData(data);
+ }
+ };
+
+ const mouseDevice = vm.runtime.ioDevices.mouse;
+ const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
+ mouseDevice.postData = (data) => {
+ if (!isPointerLockEnabled) {
+ return originalPostIOData(data);
+ }
+ };
- document.addEventListener(
- "mousedown",
- (e) => {
- // @ts-expect-error
- if (threeRenderer.domElement.contains(e.target)) {
+ document.addEventListener(
+ "mousedown",
+ (e) => {
+ // @ts-expect-error
+ if (threeRenderer.domElement.contains(e.target)) {
+ if (isLocked) {
+ postMouseData(e, true);
+ } else if (isPointerLockEnabled) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mouseup",
+ (e) => {
if (isLocked) {
- postMouseData(e, true);
- } else if (isPointerLockEnabled) {
+ postMouseData(e, false);
+ // @ts-expect-error
+ } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
threeRenderer.domElement.requestPointerLock();
}
+ },
+ true
+ );
+ document.addEventListener(
+ "mousemove",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e);
+ }
+ },
+ true
+ );
+
+ document.addEventListener("pointerlockchange", () => {
+ isLocked = document.pointerLockElement === threeRenderer.domElement;
+ });
+ document.addEventListener("pointerlockerror", (e) => {
+ console.error("Pointer lock error", e);
+ });
+
+ const oldStep = vm.runtime._step;
+ vm.runtime._step = function(...args) {
+ const ret = oldStep.call(this, ...args);
+ if (isPointerLockEnabled) {
+ const {
+ width,
+ height
+ } = rect;
+ mouse._clientX = width / 2;
+ mouse._clientY = height / 2;
+ mouse._scratchX = 0;
+ mouse._scratchY = 0;
}
- },
- true
- );
- document.addEventListener(
- "mouseup",
- (e) => {
- if (isLocked) {
- postMouseData(e, false);
- // @ts-expect-error
- } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
- threeRenderer.domElement.requestPointerLock();
- }
- },
- true
- );
- document.addEventListener(
- "mousemove",
- (e) => {
+ return ret;
+ };
+
+ vm.runtime.on("PROJECT_LOADED", () => {
+ isPointerLockEnabled = false;
if (isLocked) {
- postMouseData(e);
+ document.exitPointerLock();
}
- },
- true
- );
-
- document.addEventListener("pointerlockchange", () => {
- isLocked = document.pointerLockElement === threeRenderer.domElement;
- });
- document.addEventListener("pointerlockerror", (e) => {
- console.error("Pointer lock error", e);
- });
-
- const oldStep = vm.runtime._step;
- vm.runtime._step = function (...args) {
- const ret = oldStep.call(this, ...args);
- if (isPointerLockEnabled) {
- const { width, height } = rect;
- mouse._clientX = width / 2;
- mouse._clientY = height / 2;
- mouse._scratchX = 0;
- mouse._scratchY = 0;
- }
- return ret;
- };
+ });
- vm.runtime.on("PROJECT_LOADED", () => {
- isPointerLockEnabled = false;
- if (isLocked) {
- document.exitPointerLock();
- }
- });
-
- class Pointerlock {
- getInfo() {
- return {
- id: "threepointerlockmod",
- name: "Pointerlock for Extra 3D",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setLocked", blockType: Scratch.BlockType.COMMAND, text: "set pointer lock [enabled]", arguments: { enabled: { type: Scratch.ArgumentType.STRING, defaultValue: "true", menu: "enabled"}},},
- {opcode: "isLocked", blockType: Scratch.BlockType.BOOLEAN, text: "pointer locked?",},
- ],
- menus: {
- enabled: {acceptReporters: true, items: [
- {text: "enabled", value: "true"},{text: "disabled", value: "false"},
- ]}
- },
+ class Pointerlock {
+ getInfo() {
+ return {
+ id: "threepointerlockmod",
+ name: "Pointerlock for Extra 3D",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setLocked",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set pointer lock [enabled]",
+ arguments: {
+ enabled: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ menu: "enabled",
+ },
+ },
+ },
+ {
+ opcode: "isLocked",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "pointer locked?",
+ },
+ ],
+ menus: {
+ enabled: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "true",
+ },
+ {
+ text: "disabled",
+ value: "false",
+ },
+ ],
+ },
+ },
+ };
}
- }
- setLocked(args) {
- isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
- if (!isPointerLockEnabled && isLocked) {
- document.exitPointerLock();
+ setLocked(args) {
+ isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
+ if (!isPointerLockEnabled && isLocked) {
+ document.exitPointerLock();
+ }
}
- }
- isLocked() {
- return isLocked;
+ isLocked() {
+ return isLocked;
+ }
}
- }
-Scratch.extensions.register(new Pointerlock())
-
- })
-
-
-
-
+ Scratch.extensions.register(new Pointerlock());
+ });
})(Scratch);
From 6b1cf7d4b26e8cd8f914e07fceaa2724196cbe7d Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 09:15:08 -0600
Subject: [PATCH 03/32] Update threejsD.js
---
threejsD.js | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 447584f..81f3447 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -731,6 +731,7 @@
class ThreeScene {
constructor() {
+ // expose threejs and the scenes, so other extensions and javascript can do stuff manually
this.THREE = THREE;
this.scenes = {};
}
@@ -836,17 +837,17 @@
}
newScene(args) {
- scene = new THREE.Scene();
+ const scene = new THREE.Scene();
scene.name = args.NAME;
scene.background = new THREE.Color("#222");
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {
- ...this.scenes,
- ...scene,
- };
+
+ this.scenes[scene.name] = scene; // this can overwrite an existing scene of that name, which is okay
+
resetor(0);
}
+
+
reset() {
resetor(1);
}
From 9f8d403b77877337dc6bc4763c0f0cc8adaae2f3 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 09:16:19 -0600
Subject: [PATCH 04/32] Update threejsD.js
---
threejsD.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 81f3447..0ede9da 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -838,10 +838,10 @@
newScene(args) {
const scene = new THREE.Scene();
- scene.name = args.NAME;
+ scene.name = Scratch.Cast.toString(args.NAME);
scene.background = new THREE.Color("#222");
- this.scenes[scene.name] = scene; // this can overwrite an existing scene of that name, which is okay
+ this.scenes[Scratch.Cast.toString(args.NAME)] = scene; // this can overwrite an existing scene of that name, which is okay
resetor(0);
}
From ab32d4ff76065689f252b472e38ad7104b65dad8 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 09:40:47 -0600
Subject: [PATCH 05/32] Update threejsD.js
---
threejsD.js | 212 +++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 178 insertions(+), 34 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 0ede9da..fbef9c7 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -60,7 +60,6 @@
let physicsWorld;
let threeRenderer;
- let scene;
let camera;
let eulerOrder = "YXZ";
@@ -87,7 +86,7 @@
function resetor(level) {
camera = undefined;
- composer.reset();
+ if (composer) composer.reset();
passes = {};
customEffects = [];
@@ -129,8 +128,36 @@
return [x, y, z];
}
+ // Helper function to get current scene
+ function getCurrentScene() {
+ const threeScene = runtime.ext_threeScene;
+ if (!threeScene) return null;
+ const currentSceneName = threeScene.currentSceneName;
+ return currentSceneName ? threeScene.scenes[currentSceneName] : null;
+ }
+
+ // Helper function to get scene by name
+ function getSceneByName(sceneName) {
+ const threeScene = runtime.ext_threeScene;
+ if (!threeScene) return null;
+ return threeScene.scenes[sceneName];
+ }
+
+ // Helper function to set current scene
+ function setCurrentScene(sceneName) {
+ const threeScene = runtime.ext_threeScene;
+ if (!threeScene) return;
+ threeScene.currentSceneName = sceneName;
+ }
+
//objects
function createObject(name, content, parentName) {
+ const scene = getCurrentScene();
+ if (!scene) {
+ alerts ? alert("No active scene! Create a scene first!") : null;
+ return;
+ }
+
let object = getObject(name, true);
if (object) {
removeObject(name);
@@ -138,13 +165,25 @@
}
content.name = name;
content.rotation._order = eulerOrder;
- parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+
+ let parentObject;
+ if (parentName === scene.name) {
+ parentObject = scene;
+ } else {
+ parentObject = getObject(parentName);
+ if (!parentObject) {
+ parentObject = scene;
+ }
+ }
+
content.physics = false;
-
- object.add(content);
+ parentObject.add(content);
}
function removeObject(name) {
+ const scene = getCurrentScene();
+ if (!scene) return;
+
let object = getObject(name);
if (!object) return;
@@ -162,15 +201,16 @@
}
function getObject(name, isNew) {
- let object = null;
+ const scene = getCurrentScene();
if (!scene) {
alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
- return;
+ return null;
}
- object = scene.getObjectByName(name);
+
+ let object = scene.getObjectByName(name);
if (!object && !isNew) {
alerts ? alert(name + " does not exist! Add it to scene") : null;
- return;
+ return null;
}
return object;
}
@@ -178,7 +218,10 @@
//materials
function encodeCostume(name) {
if (name.startsWith("data:image/")) return name;
- return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ const editingTarget = vm.editingTarget;
+ if (!editingTarget) return null;
+ const costume = editingTarget.sprite.costumes.find((c) => c.name === name);
+ return costume ? costume.asset.encodeDataURI() : null;
}
function setTexutre(texture, mode, style, x, y) {
@@ -252,6 +295,7 @@
}
//composer
function updateComposers() {
+ const scene = getCurrentScene();
if (!camera || !scene) return; // nothing to do yet
// always recreate the RenderPass to point to the current scene/camera
@@ -563,8 +607,15 @@
const loop = () => {
if (!running) return;
+
+ const scene = getCurrentScene();
+ if (!scene) {
+ loopId = requestAnimationFrame(loop);
+ return;
+ }
+
//RAPIER
- if (physicsWorld && scene) {
+ if (physicsWorld) {
physicsWorld.step();
scene.children.forEach((obj) => {
@@ -575,7 +626,7 @@
}
});
}
- if (scene && camera) {
+ if (camera) {
if (controls) controls.update();
const delta = clock.getDelta();
@@ -634,7 +685,7 @@
const h = canvas.height;
threeRenderer.setSize(w, h);
- composer.setSize(w, h);
+ if (composer) composer.setSize(w, h);
customEffects.forEach((e) => {
if (e.uniforms.get("resolution")) {
e.uniforms.get("resolution").value.set(w, h);
@@ -734,6 +785,7 @@
// expose threejs and the scenes, so other extensions and javascript can do stuff manually
this.THREE = THREE;
this.scenes = {};
+ this.currentSceneName = null;
}
getInfo() {
@@ -838,27 +890,42 @@
newScene(args) {
const scene = new THREE.Scene();
- scene.name = Scratch.Cast.toString(args.NAME);
+ const sceneName = Scratch.Cast.toString(args.NAME);
+ scene.name = sceneName;
scene.background = new THREE.Color("#222");
- this.scenes[Scratch.Cast.toString(args.NAME)] = scene; // this can overwrite an existing scene of that name, which is okay
+ this.scenes[sceneName] = scene;
+ this.currentSceneName = sceneName; // Set as current scene
resetor(0);
}
-
-
reset() {
resetor(1);
+ this.scenes = {};
+ this.currentSceneName = null;
}
async setSceneProperty(args) {
+ const scene = getCurrentScene();
+ if (!scene) {
+ alerts ? alert("No active scene! Create a scene first!") : null;
+ return;
+ }
+
const property = args.PROPERTY;
const value = getAsset(args.VALUE);
scene[property] = value;
}
+
getSceneObjects(args) {
+ const scene = getCurrentScene();
+ if (!scene) {
+ alerts ? alert("No active scene! Create a scene first!") : null;
+ return "[]";
+ }
+
const names = [];
if (args.THING === "Objects") {
scene.traverse((obj) => {
@@ -866,7 +933,7 @@
});
} else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ else if (args.THING === "Lights") return JSON.stringify(Object.keys(lights));
else if (args.THING === "Scene Properties") {
console.log(scene);
return "check console";
@@ -1075,11 +1142,13 @@
}
setCamera(args) {
let object = getObject(args.CAMERA);
+ if (!object) return;
object[args.PROPERTY] = args.VALUE;
object.updateProjectionMatrix();
}
getCamera(args) {
let object = getObject(args.CAMERA);
+ if (!object) return "null";
const value = JSON.stringify(object[args.PROPERTY]);
return value;
}
@@ -1112,6 +1181,8 @@
renderTarget(args) {
let object = getObject(args.CAMERA);
+ if (!object) return;
+
const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
generateMipmaps: false,
});
@@ -1120,19 +1191,25 @@
target: renderTarget,
camera: object,
};
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ assets.renderTargets[renderTarget.texture.uuid] = renderTarget.texture;
}
sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H);
+ const target = renderTargets[args.RT];
+ if (!target) return;
+ target.target.setSize(args.W, args.H);
}
getTarget(args) {
- const t = renderTargets[args.RT].target.texture;
+ const target = renderTargets[args.RT];
+ if (!target) return "null";
+ const t = target.target.texture;
console.log(t, renderTargets[args.RT]);
return `renderTargets/${t.uuid}`;
}
removeTarget(args) {
- delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
- renderTargets[args.RT].target.dispose();
+ const target = renderTargets[args.RT];
+ if (!target) return;
+ delete assets.renderTargets[target.target.texture.uuid];
+ target.target.dispose();
delete renderTargets[args.RT];
}
}
@@ -2195,12 +2272,15 @@
}
cloneObject(args) {
let object = getObject(args.OBJECT3D);
+ if (!object) return;
const clone = object.clone(true);
clone.name;
createObject(args.NAME, clone, args.GROUP);
}
setObjectV3(args) {
let object = getObject(args.OBJECT3D);
+ if (!object) return;
+
let values = JSON.parse(args.VALUE);
function degToRad(deg) {
@@ -2274,7 +2354,7 @@
*/
getObjectV3(args) {
let object = getObject(args.OBJECT3D);
- if (!object) return;
+ if (!object) return "[0,0,0]";
let values = vector3ToString(object[args.PROPERTY]);
if (args.PROPERTY === "rotation") {
const toDeg = Math.PI / 180;
@@ -2285,6 +2365,8 @@
}
setObject(args) {
let object = getObject(args.OBJECT3D);
+ if (!object) return;
+
let value = args.VALUE;
if (args.PROPERTY === "material") {
const mat = materials[args.NAME];
@@ -2301,7 +2383,7 @@
}
getObject(args) {
let object = getObject(args.OBJECT3D);
- if (!object) return;
+ if (!object) return "null";
let value;
if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
else value = object.visible;
@@ -2312,6 +2394,8 @@
removeObject(args.OBJECT3D);
}
objectE(args) {
+ const scene = getCurrentScene();
+ if (!scene) return false;
return scene.children.map((o) => o.name).includes(args.NAME);
}
@@ -2326,6 +2410,7 @@
async setMaterial(args) {
if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
const mat = materials[args.NAME];
+ if (!mat) return;
let value = args.VALUE;
@@ -2344,17 +2429,20 @@
}
setBlending(args) {
const mat = materials[args.NAME];
+ if (!mat) return;
mat.blending = THREE[args.VALUE];
mat.premultipliedAlpha = true;
mat.needsUpdate = true;
}
setDepth(args) {
const mat = materials[args.NAME];
+ if (!mat) return;
mat.depthFunc = THREE[args.VALUE];
mat.needsUpdate = true;
}
removeMaterial(args) {
const mat = materials[args.NAME];
+ if (!mat) return;
mat.dispose();
delete materials[args.NAME];
}
@@ -2371,12 +2459,14 @@
}
setGeometry(args) {
const geo = geometries[args.NAME];
+ if (!geo) return;
geo[args.PROPERTY] = args.VALUE;
geo.needsUpdate = true;
}
removeGeometry(args) {
const geo = geometries[args.NAME];
+ if (!geo) return;
geo.dispose();
delete geometries[args.NAME];
}
@@ -2391,6 +2481,7 @@
}
async geoPoints(args) {
const geometry = geometries[args.NAME];
+ if (!geometry) return;
const positions = args.POINTS.split(" ")
.map((v) => JSON.parse(v))
.flat(); //array of v3 of each vertex of each triangle
@@ -2402,6 +2493,7 @@
}
geoUVs(args) {
const geometry = geometries[args.NAME];
+ if (!geometry) return;
const UVs = args.POINTS.split(" ")
.map((v) => JSON.parse(v))
.flat(); //array of v2 of each UV of each triangle
@@ -2691,7 +2783,9 @@
if (light.type === "PointLight") return;
//Directional & Spot Light
light.target.position.set(0, 0, 0);
- scene.add(light.target);
+
+ const scene = getCurrentScene();
+ if (scene) scene.add(light.target);
light.pos = new THREE.Vector3(0, 0, 0);
@@ -2710,7 +2804,8 @@
setLight(args) {
const light = lights[args.NAME];
- if (!args.PROPERTY) return;
+ if (!light || !args.PROPERTY) return;
+
if (args.PROPERTY === "target") {
light.target.position.set(...JSON.parse(args.VALUE)); //vector3
light.target.updateMatrixWorld();
@@ -3187,6 +3282,7 @@
}
async newTexture(args) {
const textureURI = encodeCostume(args.COSTUME);
+ if (!textureURI) return "null";
const texture = await new THREE.TextureLoader().loadAsync(textureURI);
texture.name = args.COSTUME;
@@ -3203,6 +3299,9 @@
encodeCostume(args.COSTUMEZ0),
encodeCostume(args.COSTUMEZ1),
];
+ // Check if all URIs are valid
+ if (uris.some(uri => !uri)) return "null";
+
const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
@@ -3214,6 +3313,7 @@
}
async newEquirectangularTexture(args) {
const textureURI = encodeCostume(args.COSTUME);
+ if (!textureURI) return "null";
const texture = await new THREE.TextureLoader().loadAsync(textureURI);
texture.name = args.COSTUME;
texture.mapping = THREE.EquirectangularReflectionMapping;
@@ -3254,6 +3354,9 @@
}
raycast(args) {
+ const scene = getCurrentScene();
+ if (!scene) return;
+
const origin = new THREE.Vector3(...JSON.parse(args.V3));
// rotation is in degrees => convert to radians first
const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
@@ -3484,7 +3587,7 @@
createObject(args.NAME, group, args.GROUP);
}
getModel(args) {
- if (!models[args.NAME]) return;
+ if (!models[args.NAME]) return "null";
return Object.keys(models[args.NAME].actions).toString();
}
@@ -3788,6 +3891,7 @@
}
bloom(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
@@ -3806,11 +3910,13 @@
}
godRays(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
}
let object = getObject(args.NAME);
+ if (!object) return;
const sun = object;
const godRays = new GodRaysEffect(camera, sun, {
@@ -3828,6 +3934,7 @@
}
dots(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
@@ -3843,6 +3950,7 @@
}
depth(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
@@ -4584,6 +4692,7 @@
};
}
joint(jointData, bodyA, bodyB) {
+ if (!physicsWorld || !bodyA || !bodyB) return;
physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
}
@@ -4702,6 +4811,7 @@
}
getWorld(args) {
+ if (!physicsWorld) return "null";
if (args.PROPERTY === "log") {
console.log(physicsWorld);
return "logged";
@@ -4713,26 +4823,32 @@
let value = args.VALUE;
if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
object.rigidBody[args.PROPERTY](value);
}
setC(args) {
let value = args.VALUE;
if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
let object = getObject(args.OBJECT);
+ if (!object || !object.collider) return;
object.collider[args.PROPERTY](value);
}
getRB(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return "null";
return JSON.stringify(object.rigidBody[args.PROPERTY]());
}
getC(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.collider) return "null";
return JSON.stringify(object.collider[args.PROPERTY]());
}
lockObjectAxis(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
+
const x = !JSON.parse(args.X);
const y = !JSON.parse(args.Y);
const z = !JSON.parse(args.Z);
@@ -4741,15 +4857,25 @@
objectPhysics(args) {
let object = getObject(args.OBJECT);
+ if (!object) return;
+
object.physics = JSON.parse(args.state);
if (JSON.parse(args.state)) {
//if already exists delete:
if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody);
+ if (physicsWorld) {
+ physicsWorld.removeRigidBody(object.rigidBody);
+ }
object.rigidBody = null;
object.collider = null;
}
+
+ if (!physicsWorld) {
+ console.error("Physics world not created!");
+ return;
+ }
+
/*asing a rigidbody and collider to object and add them to physicsWorld*/
let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
.setTranslation(object.position.x, object.position.y, object.position.z)
@@ -4774,7 +4900,13 @@
case "trimesh":
colliderDesc = TriMesh(object);
break;
+ default:
+ console.error("Unknown collider type:", args.collider);
+ return;
}
+
+ if (!colliderDesc) return;
+
colliderDesc
.setSensor(JSON.parse(args.state2))
.setMass(args.mass)
@@ -4788,7 +4920,9 @@
object.collider = collider;
} else {
/*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody);
+ if (physicsWorld && object.rigidBody) {
+ physicsWorld.removeRigidBody(object.rigidBody);
+ }
object.rigidBody = null;
object.collider = null;
}
@@ -4796,7 +4930,7 @@
enableCCD(args) {
let object = getObject(args.OBJECT);
- if (object.physics) {
+ if (object && object.physics && object.rigidBody) {
let rigidBody = object.rigidBody;
rigidBody.enableCcd(JSON.parse(args.state));
}
@@ -4804,6 +4938,8 @@
addForce(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
+
const vector = JSON.parse(args.VALUE).map(Number);
let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
@@ -4815,13 +4951,18 @@
}
resetForces(args) {
- rigidBody[args.PROPERTY](true);
+ let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
+
+ object.rigidBody[args.PROPERTY](true);
}
sensorSingle(args) {
const sensor = getObject(args.SENSOR);
+ if (!sensor || !sensor.collider || !physicsWorld) return false;
let object = getObject(args.OBJECT);
+ if (!object || !object.collider) return false;
let touching = false;
physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
@@ -4833,14 +4974,17 @@
sensorAll(args) {
const sensor = getObject(args.SENSOR);
+ if (!sensor || !sensor.collider || !physicsWorld) return "[]";
const touchedObjects = [];
- // loop thruogh every collider touching sensor
+ // loop through every collider touching sensor
physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
// find owner of collider
+ const scene = getCurrentScene();
+ if (!scene) return;
+
const otherObject = scene.children.find((o) => o.collider === otherCollider);
- console.log(otherCollider);
if (otherObject) touchedObjects.push(otherObject.name);
});
From dec00ad042c865607fac0e24f34d37ab14005a2a Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 20:44:19 -0600
Subject: [PATCH 06/32] Update threejsD.js
---
threejsD.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/threejsD.js b/threejsD.js
index fbef9c7..90ff344 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -545,6 +545,7 @@
stencil: false,
depth: true,
});
+ window.__THREE_RENDERER__ = threeRenderer;
threeRenderer.setPixelRatio(window.devicePixelRatio);
threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
From 23f2ee20b4aaa7037b00e02cf3a61f2020f9a8e5 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 20:52:47 -0600
Subject: [PATCH 07/32] Update threejsD.js
---
threejsD.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/threejsD.js b/threejsD.js
index 90ff344..186548c 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -491,6 +491,7 @@
//loops/init
function stopLoop() {
if (!running) return;
+ vm.renderer.canvas.style.visibility="visible";
running = false;
if (loopId) {
@@ -604,6 +605,7 @@
function startRenderLoop() {
if (running) return;
+ vm.renderer.canvas.style.visibility="hidden";
running = true;
const loop = () => {
From 930b4e847983db736f94706e1dd4da1889bce73d Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 12:07:48 -0600
Subject: [PATCH 08/32] Update threejsD.js
---
threejsD.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/threejsD.js b/threejsD.js
index 186548c..e628135 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -69,7 +69,9 @@
let renderTargets = {};
let materials = {};
+ window.__THREE_MATERIALS__ = materials;
let geometries = {};
+ window.__THREE_GEOMETRIES__ = geometries;
let lights = {};
let models = {};
From 8a85ec952dc23ef7cfab5015119c1b46d5769e5b Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 12:11:17 -0600
Subject: [PATCH 09/32] Update threejsD.js
---
threejsD.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/threejsD.js b/threejsD.js
index e628135..d08c71e 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -95,7 +95,9 @@
renderTargets = {};
materials = {};
+ window.__THREE_MATERIALS__ = materials;
geometries = {};
+ window.__THREE_GEOMETRIES__ = geometries;
lights = {};
models = {};
From 293ed4d41da5de52bf711403747ed7278b49bee8 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 12:14:25 -0600
Subject: [PATCH 10/32] Update threejsD.js
---
threejsD.js | 44 +++++++++++++++++++-------------------------
1 file changed, 19 insertions(+), 25 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index d08c71e..c0e07de 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -67,11 +67,8 @@
let passes = {};
let customEffects = [];
let renderTargets = {};
-
- let materials = {};
- window.__THREE_MATERIALS__ = materials;
- let geometries = {};
- window.__THREE_GEOMETRIES__ = geometries;
+ window.__THREE_MATERIALS__ = {};
+ window.__THREE_GEOMETRIES__ = {}
let lights = {};
let models = {};
@@ -93,11 +90,8 @@
passes = {};
customEffects = [];
renderTargets = {};
-
- materials = {};
- window.__THREE_MATERIALS__ = materials;
- geometries = {};
- window.__THREE_GEOMETRIES__ = geometries;
+ window.__THREE_MATERIALS__ = {};
+ window.__THREE_GEOMETRIES__ = {};
lights = {};
models = {};
@@ -938,8 +932,8 @@
scene.traverse((obj) => {
if (obj.name) names.push(obj.name); //if it has a name, add to list!
});
- } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(window.__THREE_MATERIALS__));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(window.__THREE_GEOMETRIES__));
else if (args.THING === "Lights") return JSON.stringify(Object.keys(lights));
else if (args.THING === "Scene Properties") {
console.log(scene);
@@ -2380,7 +2374,7 @@
if (mat) value = mat;
else value = undefined;
} else if (args.PROPERTY === "geometry") {
- const geo = geometries[args.NAME];
+ const geo = window.__THREE_GEOMETRIES__[args.NAME];
if (geo) value = geo;
else value = undefined;
} else value = !!value;
@@ -2458,36 +2452,36 @@
}
newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ if (window.__THREE_GEOMETRIES__[args.NAME] && alerts) alert("geometry already exists! will replace...");
const geo = new THREE[args.TYPE]();
geo.name = args.NAME;
- geometries[args.NAME] = geo;
+ window.__THREE_GEOMETRIES__[args.NAME] = geo;
}
setGeometry(args) {
- const geo = geometries[args.NAME];
+ const geo = window.__THREE_GEOMETRIES__[args.NAME];
if (!geo) return;
geo[args.PROPERTY] = args.VALUE;
geo.needsUpdate = true;
}
removeGeometry(args) {
- const geo = geometries[args.NAME];
+ const geo = window.__THREE_GEOMETRIES__[args.NAME];
if (!geo) return;
geo.dispose();
- delete geometries[args.NAME];
+ delete window.__THREE_GEOMETRIES__[args.NAME];
}
geometryE(args) {
- return geometries[args.NAME] ? true : false;
+ return window.__THREE_GEOMETRIES__[args.NAME] ? true : false;
}
newGeo(args) {
const geometry = new THREE.BufferGeometry();
geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
+ window.__THREE_GEOMETRIES__[args.NAME] = geometry;
}
async geoPoints(args) {
- const geometry = geometries[args.NAME];
+ const geometry = window.__THREE_GEOMETRIES__[args.NAME];
if (!geometry) return;
const positions = args.POINTS.split(" ")
.map((v) => JSON.parse(v))
@@ -2499,7 +2493,7 @@
geometry.needsUpdate = true;
}
geoUVs(args) {
- const geometry = geometries[args.NAME];
+ const geometry = window.__THREE_GEOMETRIES__[args.NAME];
if (!geometry) return;
const UVs = args.POINTS.split(" ")
.map((v) => JSON.parse(v))
@@ -2513,7 +2507,7 @@
const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
+ window.__THREE_GEOMETRIES__[args.NAME] = geometry;
}
async splineModel(args) {
@@ -2563,7 +2557,7 @@
merged.computeBoundingSphere();
merged.name = args.NAME;
- geometries[args.NAME] = merged;
+ window.__THREE_GEOMETRIES__[args.NAME] = merged;
matList.name = args.NAME;
materials[args.NAME] = matList;
}
@@ -2593,7 +2587,7 @@
geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
+ window.__THREE_GEOMETRIES__[args.NAME] = geometry;
}
async loadFont() {
From bcb905793defdadf8a482d3925fca113bdea985a Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 12:15:36 -0600
Subject: [PATCH 11/32] Update threejsD.js
---
threejsD.js | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index c0e07de..ad7673c 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -2370,7 +2370,7 @@
let value = args.VALUE;
if (args.PROPERTY === "material") {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
if (mat) value = mat;
else value = undefined;
} else if (args.PROPERTY === "geometry") {
@@ -2402,15 +2402,15 @@
//defines
newMaterial(args) {
- if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ if (window.__THREE_MATERIALS__[args.NAME] && alerts) alert("material already exists! will replace...");
const mat = new THREE[args.TYPE]();
mat.name = args.NAME;
- materials[args.NAME] = mat;
+ window.__THREE_MATERIALS__[args.NAME] = mat;
}
async setMaterial(args) {
if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
if (!mat) return;
let value = args.VALUE;
@@ -2429,26 +2429,26 @@
mat.needsUpdate = true;
}
setBlending(args) {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
if (!mat) return;
mat.blending = THREE[args.VALUE];
mat.premultipliedAlpha = true;
mat.needsUpdate = true;
}
setDepth(args) {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
if (!mat) return;
mat.depthFunc = THREE[args.VALUE];
mat.needsUpdate = true;
}
removeMaterial(args) {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
if (!mat) return;
mat.dispose();
- delete materials[args.NAME];
+ delete window.__THREE_MATERIALS__[args.NAME];
}
materialE(args) {
- return materials[args.NAME] ? true : false;
+ return window.__THREE_MATERIALS__[args.NAME] ? true : false;
}
newGeometry(args) {
@@ -2559,7 +2559,7 @@
merged.name = args.NAME;
window.__THREE_GEOMETRIES__[args.NAME] = merged;
matList.name = args.NAME;
- materials[args.NAME] = matList;
+ window.__THREE_MATERIALS__[args.NAME] = matList;
}
async text(args) {
From 3db54fdb1219ad5561bdf3bb3de6fd8d73a269c4 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Mon, 15 Dec 2025 08:55:05 -0600
Subject: [PATCH 12/32] Update threejsD.js
---
threejsD.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/threejsD.js b/threejsD.js
index 2d9823c..27ba380 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -643,6 +643,11 @@ Promise.resolve(load()).then(() => {
Scratch.extensions.register(new ThreeRenderer())
class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
getInfo() {
return {
id: "threeScene",
@@ -674,7 +679,7 @@ Promise.resolve(load()).then(() => {
scene.name = args.NAME
scene.background = new THREE.Color("#222")
//scene.add(new THREE.GridHelper(16, 16)) //future helper section?
-
+ this.scenes = {...this.scenes, {scene}};
resetor(0)
}
From b813f957ef146b9defa1e37a696c3253ac164391 Mon Sep 17 00:00:00 2001
From: FreshPenguin112
Date: Tue, 16 Dec 2025 16:59:54 -0600
Subject: [PATCH 13/32] merge
---
threejsD.js | 6475 ++++++++++++++++++++++++++------------
threejsD.js.orig | 5029 +++++++++++++++++++++++++++++
threejsD_BACKUP_13852.js | 5029 +++++++++++++++++++++++++++++
threejsD_BASE_13852.js | 2413 ++++++++++++++
threejsD_LOCAL_13852.js | 2414 ++++++++++++++
threejsD_REMOTE_13852.js | 5016 +++++++++++++++++++++++++++++
6 files changed, 24439 insertions(+), 1937 deletions(-)
create mode 100644 threejsD.js.orig
create mode 100644 threejsD_BACKUP_13852.js
create mode 100644 threejsD_BASE_13852.js
create mode 100644 threejsD_LOCAL_13852.js
create mode 100644 threejsD_REMOTE_13852.js
diff --git a/threejsD.js b/threejsD.js
index 27ba380..bc6db57 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -1,88 +1,102 @@
+/* jshint esversion: 11 */
// Name: Extra 3D
// ID: threejsExtension
-// Description: Use three js inside Turbowarp! A 3D graphics library.
+// Description: Use three js inside Turbowarp! A 3D graphics library.
// By: Civero
// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
-(function (Scratch) {
+(function(Scratch) {
"use strict";
if (!Scratch.extensions.unsandboxed) {
throw new Error("Three-D extension must run unsandboxed");
}
- if (Scratch.vm.runtime.isPackaged) {alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`); return}
+ if (Scratch.vm.runtime.isPackaged) {
+ alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
+ return;
+ }
//if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
const vm = Scratch.vm;
- const runtime = vm.runtime
+ const runtime = vm.runtime;
const renderer = Scratch.renderer;
- const canvas = renderer.canvas
+ const canvas = renderer.canvas;
const Cast = Scratch.Cast;
- const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
-
- let alerts = false
- console.log("alerts are "+ (alerts ? "enabled" : "disabled"))
+ const menuIconURI =
+ "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
+
+ let alerts = false;
+ console.log("alerts are " + (alerts ? "enabled" : "disabled"));
- let isMouseDown = { left: false, middle: false, right: false }
- let prevMouse = { left: false, middle: false, right: false }
+ let isMouseDown = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+ let prevMouse = {
+ left: false,
+ middle: false,
+ right: false,
+ };
- let lastWidth = 0
- let lastHeight = 0
+ let lastWidth = 0;
+ let lastHeight = 0;
- let THREE
- let clock
- let running
- let loopId
+ let THREE;
+ let clock;
+ let running;
+ let loopId;
//Addons
- let GLTFLoader
- let gltf
- let OrbitControls
- let controls
- let BufferGeometryUtils
- let TextGeometry
- let fontLoad
+ let GLTFLoader;
+ let gltf;
+ let OrbitControls;
+ let controls;
+ let BufferGeometryUtils;
+ let TextGeometry;
+ let fontLoad;
//Physics
- let RAPIER
- let physicsWorld
-
- let threeRenderer
- let scene
- let camera
- let eulerOrder = "YXZ"
-
- let composer
- let passes = {}
- let customEffects = []
- let renderTargets = {}
-
- let materials = {}
- let geometries = {}
- let lights = {}
- let models = {}
-
- let assets = { //should i place materials, geometries; inside too?
+ let RAPIER;
+ let physicsWorld;
+
+ let threeRenderer;
+ let scene;
+ let camera;
+ let eulerOrder = "YXZ";
+
+ let composer;
+ let passes = {};
+ let customEffects = [];
+ let renderTargets = {};
+
+ let materials = {};
+ let geometries = {};
+ let lights = {};
+ let models = {};
+
+ let assets = {
+ //should i place materials, geometries; inside too?
textures: {},
colors: {},
fogs: {},
curves: {},
renderTargets: {}, //not the same as the global one! this one only stores textures
- }
+ };
- let raycastResult = []
+ let raycastResult = [];
function resetor(level) {
- camera = undefined
- composer.reset()
+ camera = undefined;
+ composer.reset();
- passes = {}
- customEffects = []
- renderTargets = {}
+ passes = {};
+ customEffects = [];
+ renderTargets = {};
- materials = {}
- geometries = {}
- lights = {}
- models = {}
+ materials = {};
+ geometries = {};
+ lights = {};
+ models = {};
if (level > 0) {
assets = {
@@ -91,124 +105,140 @@
fogs: {},
curves: {},
renderTargets: {},
- }
+ };
}
- updateComposers()
+ updateComposers();
}
-
-//utility
- function vector3ToString(prop) {
- if (!prop) return "0,0,0";
- const x = (typeof(prop.x) === "number") ? prop.x : (typeof(prop._x) === "number") ? prop._x : (JSON.stringify(prop).includes("X")) ? prop : 0
- const y = (typeof(prop.y) === "number") ? prop.y : (typeof(prop.y) === "number") ? prop._y : 0
- const z = (typeof(prop.z) === "number") ? prop.z : (typeof(prop.z) === "number") ? prop.z : 0
+ //utility
+ function vector3ToString(prop) {
+ if (!prop) return "0,0,0";
+
+ const x =
+ typeof prop.x === "number" ?
+ prop.x :
+ typeof prop._x === "number" ?
+ prop._x :
+ JSON.stringify(prop).includes("X") ?
+ prop :
+ 0;
+ const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
+ const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
+
+ return [x, y, z];
+ }
- return [x, y, z]
+ //objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true);
+ if (object) {
+ removeObject(name);
+ alerts ? alert(name + " already exsisted, will replace!") : null;
}
+ content.name = name;
+ content.rotation._order = eulerOrder;
+ parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+ content.physics = false;
-//objects
- function createObject(name, content, parentName) {
- let object = getObject(name, true)
- if (object) {
- removeObject(name)
- alerts ? alert(name + " already exsisted, will replace!") : null
- }
- content.name = name
- content.rotation._order = eulerOrder
- parentName === scene.name ? object = scene : object = getObject(parentName)
- content.physics = false
+ object.add(content);
+ }
- object.add(content)
- }
- function removeObject(name) {
- let object = getObject(name)
- if (!object) return
+ function removeObject(name) {
+ let object = getObject(name);
+ if (!object) return;
- scene.remove(object)
+ scene.remove(object);
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true)
- physicsWorld.removeRigidBody(object.rigidBody, true)
- object.rigidBody = null
- object.collider = null
- }
- if (object.isLight) {
- delete(lights[name])
- }
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true);
+ physicsWorld.removeRigidBody(object.rigidBody, true);
+ object.rigidBody = null;
+ object.collider = null;
}
- function getObject(name, isNew) {
- let object = null
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null; return;}
- object = scene.getObjectByName(name)
- if (!object && !isNew) {alerts ? alert(name + " does not exist! Add it to scene"):null; return;}
- return object
+ if (object.isLight) {
+ delete lights[name];
}
+ }
-//materials
- function encodeCostume (name) {
- if (name.startsWith("data:image/")) return name
- return Scratch.vm.editingTarget.sprite.costumes.find(c => c.name === name).asset.encodeDataURI()
+ function getObject(name, isNew) {
+ let object = null;
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
+ return;
+ }
+ object = scene.getObjectByName(name);
+ if (!object && !isNew) {
+ alerts ? alert(name + " does not exist! Add it to scene") : null;
+ return;
}
- function setTexutre (texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace
+ return object;
+ }
+
+ //materials
+ function encodeCostume(name) {
+ if (name.startsWith("data:image/")) return name;
+ return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ }
+
+ function setTexutre(texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace;
- if (mode === "Pixelate") {
+ if (mode === "Pixelate") {
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
- } else { //Blur
- texture.minFilter = THREE.NearestMipmapLinearFilter
- texture.magFilter = THREE.NearestMipmapLinearFilter
- }
-
- if (style === "Repeat") {
- texture.wrapS = THREE.RepeatWrapping
- texture.wrapT = THREE.RepeatWrapping
- texture.repeat.set(x, y)
- }
+ } else {
+ //Blur
+ texture.minFilter = THREE.NearestMipmapLinearFilter;
+ texture.magFilter = THREE.NearestMipmapLinearFilter;
+ }
- texture.generateMipmaps = true;
+ if (style === "Repeat") {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(x, y);
}
- async function resizeImageToSquare(uri, size = 256) {
+
+ texture.generateMipmaps = true;
+ }
+ async function resizeImageToSquare(uri, size = 256) {
return new Promise((resolve) => {
- const img = new Image()
- img.onload = () => {
- const canvas = document.createElement('canvas')
- canvas.width = size
- canvas.height = size
- const ctx = canvas.getContext('2d')
-
- // clear + draw image scaled to fit canvas
- ctx.clearRect(0, 0, size, size)
- ctx.drawImage(img, 0, 0, size, size)
-
- resolve(canvas.toDataURL()) // return normalized Data URI
- //delete canvas?
- };
- img.src = uri
- });
-}
-//light
-function updateShadowFrustum(light, focusPos) {
- if (light.type !== "DirectionalLight") return
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = size;
+ canvas.height = size;
+ const ctx = canvas.getContext("2d");
+
+ // clear + draw image scaled to fit canvas
+ ctx.clearRect(0, 0, size, size);
+ ctx.drawImage(img, 0, 0, size, size);
+
+ resolve(canvas.toDataURL()); // return normalized Data URI
+ //delete canvas?
+ };
+ img.src = uri;
+ });
+ }
+ //light
+ function updateShadowFrustum(light, focusPos) {
+ if (light.type !== "DirectionalLight") return;
// Frustum Size - Increase this value to cover a larger area.
const d = 50;
// Update Orthographic Shadow Camera Frustum
const shadowCamera = light.shadow.camera;
-
+
// Set the width/height of the frustum
shadowCamera.left = -d;
shadowCamera.right = d;
shadowCamera.top = d;
shadowCamera.bottom = -d;
-
+
// Determine ranges
- shadowCamera.near = 0.1
- shadowCamera.far = 500
+ shadowCamera.near = 0.1;
+ shadowCamera.far = 500;
// Position the Light and its Target
light.target.position.copy(focusPos);
@@ -217,69 +247,79 @@ function updateShadowFrustum(light, focusPos) {
// Ensure matrices are updated.
light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix()
- light.shadow.needsUpdate = true;
-}
-//composer
-function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
-
- // always recreate the RenderPass to point to the current scene/camera
- passes["Render"] = new RenderPass(scene, camera);
-
- // ensure composer has a RenderPass as the first pass
- const hasRender = composer.passes.some(p => p && p.scene);
- if (!hasRender) composer.addPass(passes["Render"]);
- else {
- // if composer already has one, replace it so it references current scene/camera
- const idx = composer.passes.findIndex(p => p && p.scene);
- composer.passes[idx] = passes["Render"];
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
}
-}
-//utility
-function getMouseNDC(event) {
- // Use threeRenderer.domElement for correct offset
- const rect = threeRenderer.domElement.getBoundingClientRect();
- const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
- const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
- return [x, y];
-}
-function checkCanvasSize() {
- const { width, height } = canvas
- if (width !== lastWidth || height !== lastHeight) {
- lastWidth = width
- lastHeight = height
- resize()
+ //composer
+ function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
+
+ // always recreate the RenderPass to point to the current scene/camera
+ passes["Render"] = new RenderPass(scene, camera);
+
+ // ensure composer has a RenderPass as the first pass
+ const hasRender = composer.passes.some((p) => p && p.scene);
+ if (!hasRender) composer.addPass(passes["Render"]);
+ else {
+ // if composer already has one, replace it so it references current scene/camera
+ const idx = composer.passes.findIndex((p) => p && p.scene);
+ composer.passes[idx] = passes["Render"];
+ }
+ }
+ //utility
+ function getMouseNDC(event) {
+ // Use threeRenderer.domElement for correct offset
+ const rect = threeRenderer.domElement.getBoundingClientRect();
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ return [x, y];
+ }
+
+ function checkCanvasSize() {
+ const {
+ width,
+ height
+ } = canvas;
+ if (width !== lastWidth || height !== lastHeight) {
+ lastWidth = width;
+ lastHeight = height;
+ resize();
+ }
+ requestAnimationFrame(checkCanvasSize); //rerun next frame
}
- requestAnimationFrame(checkCanvasSize) //rerun next frame
-}
-//physics
-function computeWorldBoundingBox(mesh) {
+ //physics
+ function computeWorldBoundingBox(mesh) {
// Create a Box3 in world coordinates
const box = new THREE.Box3().setFromObject(mesh);
const size = new THREE.Vector3();
box.getSize(size);
const center = new THREE.Vector3();
box.getCenter(center);
- return { size, center };
-}
-function createCuboidCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
- const collider = RAPIER.ColliderDesc.cuboid(
- size.x / 2,
- size.y / 2,
- size.z / 2
- )
+ return {
+ size,
+ center,
+ };
+ }
+
+ function createCuboidCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
return collider;
-}
-function createBallCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
+ }
+
+ function createBallCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
// radius = 1/2 of the largest verticie
const radius = Math.max(size.x, size.y, size.z) / 2;
- const collider = RAPIER.ColliderDesc.ball(radius)
- return collider //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
-}
-function createConvexHullCollider(mesh) {
+ const collider = RAPIER.ColliderDesc.ball(radius);
+ return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
+ }
+
+ function createConvexHullCollider(mesh) {
mesh.updateWorldMatrix(true, false);
const position = mesh.geometry.attributes.position;
@@ -287,211 +327,226 @@ function createConvexHullCollider(mesh) {
const vertex = new THREE.Vector3();
// Matrix for scale only
- const scaleMatrix = new THREE.Matrix4().makeScale(
- mesh.scale.x,
- mesh.scale.y,
- mesh.scale.z
- );
+ const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
for (let i = 0; i < position.count; i++) {
- vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
- vertices.push(vertex.x, vertex.y, vertex.z);
+ vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
+ vertices.push(vertex.x, vertex.y, vertex.z);
}
const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
return collider;
-}
-function TriMesh(mesh) {
- // Get the positions array (from your geoPoints function)
-const positions = mesh.geometry.attributes.position.array;
-const numVertices = positions.length / 3;
-
-// Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
-const indices = Array.from({ length: numVertices }, (_, i) => i);
-
-const collider = RAPIER.ColliderDesc.trimesh(
- positions,
- new Uint32Array(indices)
-);
-
-return collider
-}
-function getModel(model, name) {
- const file = runtime.getTargetForStage().getSounds().find(c => c.name === model)
- if (!file) return
-
-return new Promise((resolve, reject) => {
- gltf.parse(
- file.asset.data.buffer,
- "",
- gltf => {
- const root = gltf.scene
- root.traverse(child => {
- if (child.isMesh) {
- child.castShadow = true
- child.receiveShadow = true
- }
- });
+ }
- const mixer = new THREE.AnimationMixer(root)
- const actions = {}
- gltf.animations.forEach(clip => {
- const act = mixer.clipAction(clip)
- act.clampWhenFinished = true
- actions[clip.name] = act
- });
+ function TriMesh(mesh) {
+ // Get the positions array (from your geoPoints function)
+ const positions = mesh.geometry.attributes.position.array;
+ const numVertices = positions.length / 3;
- models[name] = { root, mixer, actions }
- resolve(root)
+ // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
+ const indices = Array.from({
+ length: numVertices,
},
- error => {
- console.error("Error parsing GLB model:", error)
- reject(error)
- }
- )})
-}
-async function openFileExplorer(format) {
- return new Promise((resolve) => {
- const input = document.createElement("input");
- input.type = "file"
- input.accept = format
- input.multiple = false
+ (_, i) => i
+ );
+
+ const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
+
+ return collider;
+ }
+
+ function getModel(model, name) {
+ const file = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === model);
+ if (!file) return;
+
+ return new Promise((resolve, reject) => {
+ gltf.parse(
+ file.asset.data.buffer,
+ "",
+ (gltf) => {
+ const root = gltf.scene;
+ root.traverse((child) => {
+ if (child.isMesh) {
+ child.castShadow = true;
+ child.receiveShadow = true;
+ }
+ });
+
+ const mixer = new THREE.AnimationMixer(root);
+ const actions = {};
+ gltf.animations.forEach((clip) => {
+ const act = mixer.clipAction(clip);
+ act.clampWhenFinished = true;
+ actions[clip.name] = act;
+ });
+
+ models[name] = {
+ root,
+ mixer,
+ actions,
+ };
+ resolve(root);
+ },
+ (error) => {
+ console.error("Error parsing GLB model:", error);
+ reject(error);
+ }
+ );
+ });
+ }
+ async function openFileExplorer(format) {
+ return new Promise((resolve) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = format;
+ input.multiple = false;
input.onchange = () => {
- resolve(input.files)
- input.remove()
+ resolve(input.files);
+ input.remove();
};
input.click();
- })
-}
-function getMeshesUsingTexture(scene, targetTexture) {
- const meshes = []
-
- scene.traverse(object => {
- if (object.material) {
- const materials = Array.isArray(object.material) ? object.material : [object.material]
- for (const material of materials) {
- if (material.map === targetTexture) {
- meshes.push(object)
- break
- }
- }
+ });
+ }
+
+ function getMeshesUsingTexture(scene, targetTexture) {
+ const meshes = [];
+
+ scene.traverse((object) => {
+ if (object.material) {
+ const materials = Array.isArray(object.material) ? object.material : [object.material];
+ for (const material of materials) {
+ if (material.map === targetTexture) {
+ meshes.push(object);
+ break;
+ }
}
- })
-
- return meshes
-}
-function getAsset(path) {
- if (typeof(path) == "string") { //string?
- if (path.includes("/")) { //has the /?
- const value = path.split("/")
- console.log(value[0], value[1])
- return assets[value[0]][value[1]]
- }
+ }
+ });
+
+ return meshes;
}
- return JSON.parse(path) //boolean or number
-}
+ function getAsset(path) {
+ if (typeof path == "string") {
+ //string?
+ if (path.includes("/")) {
+ //has the /?
+ const value = path.split("/");
+ console.log(value[0], value[1]);
+ return assets[value[0]][value[1]];
+ }
+ }
+
+ return JSON.parse(path); //boolean or number
+ }
-let mouseNDC = [0, 0]
-//loops/init
-function stopLoop() {
- if (!running) return
- running = false
+ let mouseNDC = [0, 0];
+ //loops/init
+ function stopLoop() {
+ if (!running) return;
+ running = false;
- if (loopId) {
- cancelAnimationFrame(loopId)
- loopId = null
- if (threeRenderer) threeRenderer.clear();
+ if (loopId) {
+ cancelAnimationFrame(loopId);
+ loopId = null;
+ if (threeRenderer) threeRenderer.clear();
+ }
}
-}
-async function load() {
+ async function load() {
if (!THREE) {
-
// @ts-ignore
THREE = await import("https://esm.sh/three@0.180.0")
window._THREE_ = THREE
//Addons
- GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js")
- OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js")
- BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js")
- TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js")
- const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js")
- fontLoad = new FontLoader.FontLoader()
-
- const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8")
+ GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
+ OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
+ BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
+ TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
+ const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
+ fontLoad = new FontLoader.FontLoader();
+
+ const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
const {
EffectComposer,
EffectPass,
RenderPass,
-
+
Effect,
BloomEffect,
GodRaysEffect,
DotScreenEffect,
DepthOfFieldEffect,
- BlendFunction
- } = POSTPROCESSING
- //so i can use them later as global
- window.EffectComposer = EffectComposer;
- window.EffectPass = EffectPass;
- window.RenderPass = RenderPass;
- window.Effect = Effect;
- window.BloomEffect = BloomEffect;
- window.GodRaysEffect = GodRaysEffect;
- window.DotScreenEffect = DotScreenEffect;
- window.DepthOfFieldEffect = DepthOfFieldEffect;
- window.BlendFunction = BlendFunction;
-
- RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0")
- await RAPIER.init()
-
+ BlendFunction,
+ } = POSTPROCESSING;
+ //so i can use them later as global
+ window.EffectComposer = EffectComposer;
+ window.EffectPass = EffectPass;
+ window.RenderPass = RenderPass;
+ window.Effect = Effect;
+ window.BloomEffect = BloomEffect;
+ window.GodRaysEffect = GodRaysEffect;
+ window.DotScreenEffect = DotScreenEffect;
+ window.DepthOfFieldEffect = DepthOfFieldEffect;
+ window.BlendFunction = BlendFunction;
+
+ RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
+ await RAPIER.init();
+
threeRenderer = new THREE.WebGLRenderer({
powerPreference: "high-performance",
antialias: false,
stencil: false,
- depth: true
- })
- threeRenderer.setPixelRatio(window.devicePixelRatio)
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping // HDR look (test)
+ depth: true,
+ });
+ threeRenderer.setPixelRatio(window.devicePixelRatio);
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
//threeRenderer.toneMappingExposure = 1.0 //(test)
- threeRenderer.shadowMap.enabled = true
- threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap // (optional)
- threeRenderer.domElement.style.pointerEvents = 'auto' //will disable turbowarp mouse events, but enable threejs's
+ threeRenderer.shadowMap.enabled = true;
+ threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
+ threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
+
+ gltf = new GLTFLoader.GLTFLoader();
+ clock = new THREE.Clock();
- gltf = new GLTFLoader.GLTFLoader()
- clock = new THREE.Clock()
-
// Example: create a composer
- composer = new EffectComposer(threeRenderer, {frameBufferType: THREE.HalfFloatType})
-
- renderer.addOverlay( threeRenderer.domElement, "manual" )
- renderer.addOverlay(canvas, "manual")
- renderer.setBackgroundColor(1, 1, 1, 0)
-
- resize()
-
- window.addEventListener("mousedown", e => {
- if (e.button === 0) isMouseDown.left = true
- if (e.button === 1) isMouseDown.middle = true
- if (e.button === 2) isMouseDown.right = true
- })
- window.addEventListener("mouseup", e => {
- if (e.button === 0) isMouseDown.left = false; prevMouse.left = false
- if (e.button === 1) isMouseDown.middle = false; prevMouse.middle = false
- if (e.button === 2) isMouseDown.right = false; prevMouse.right = false
- })
+ composer = new EffectComposer(threeRenderer, {
+ frameBufferType: THREE.HalfFloatType,
+ });
+
+ renderer.addOverlay(threeRenderer.domElement, "manual");
+ renderer.addOverlay(canvas, "manual");
+ renderer.setBackgroundColor(1, 1, 1, 0);
+
+ resize();
+
+ window.addEventListener("mousedown", (e) => {
+ if (e.button === 0) isMouseDown.left = true;
+ if (e.button === 1) isMouseDown.middle = true;
+ if (e.button === 2) isMouseDown.right = true;
+ });
+ window.addEventListener("mouseup", (e) => {
+ if (e.button === 0) isMouseDown.left = false;
+ prevMouse.left = false;
+ if (e.button === 1) isMouseDown.middle = false;
+ prevMouse.middle = false;
+ if (e.button === 2) isMouseDown.right = false;
+ prevMouse.right = false;
+ });
// prevent contextmenu on right click
- threeRenderer.domElement.addEventListener("contextmenu", e => e.preventDefault());
+ threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
- threeRenderer.domElement.addEventListener('mousemove', (event) => {
+ threeRenderer.domElement.addEventListener("mousemove", (event) => {
mouseNDC = getMouseNDC(event);
- })
+ });
- running = false
- load()
+ running = false;
+ load();
startRenderLoop()
runtime.on('PROJECT_START', () => startRenderLoop())
@@ -500,828 +555,1963 @@ async function load() {
checkCanvasSize()
}
}
-function startRenderLoop() {
- if (running) return
- running = true
-
- const loop = () => {
- if (!running) return
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step()
-
- scene.children.forEach(obj => {
- if (!(obj.isMesh) || !(obj.physics)) return
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation())
- obj.quaternion.copy(obj.rigidBody.rotation())
- }
- })
-
- }
- if (scene && camera) {
- if (controls) controls.update()
- const delta = clock.getDelta()
- Object.values(models).forEach( model => { if (model) model.mixer.update(delta) } )
+ function startRenderLoop() {
+ if (running) return;
+ running = true;
+
+ const loop = () => {
+ if (!running) return;
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step();
+
+ scene.children.forEach((obj) => {
+ if (!obj.isMesh || !obj.physics) return;
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation());
+ obj.quaternion.copy(obj.rigidBody.rotation());
+ }
+ });
+ }
+ if (scene && camera) {
+ if (controls) controls.update();
- Object.values(lights).forEach(light => updateShadowFrustum(light, camera.position))
+ const delta = clock.getDelta();
+ Object.values(models).forEach((model) => {
+ if (model) model.mixer.update(delta);
+ });
- //update custom effects time
- customEffects.forEach(e => {
- if (e.uniforms.get('time')) {
- e.uniforms.get('time').value += delta
- }
- })
- Object.values(renderTargets).forEach(t => {
- if ( t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height
- t.camera.updateProjectionMatrix()
- }
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
+ Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
- displayMeshes.forEach(mesh => {
- mesh.visible = false
- })
+ //update custom effects time
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("time")) {
+ e.uniforms.get("time").value += delta;
+ }
+ });
+ Object.values(renderTargets).forEach((t) => {
+ if (t.camera.type == "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height;
+ t.camera.updateProjectionMatrix();
+ }
+ // get meshes using the texture associated with this target
+ const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = false;
+ });
+
+ if (t.camera.type == "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target);
+ threeRenderer.clear(true, true, true);
+ threeRenderer.render(scene, t.camera);
+ } else {
+ t.target.clear(threeRenderer);
+ t.camera.update(threeRenderer, scene); //cubeCamera
+ }
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target)
- threeRenderer.clear(true, true, true)
- threeRenderer.render(scene, t.camera)
- } else {
- t.target.clear(threeRenderer)
- t.camera.update( threeRenderer, scene ) //cubeCamera
- }
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = true;
+ });
+ });
- displayMeshes.forEach(mesh => {
- mesh.visible = true
- })
- })
+ camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
+ camera.updateProjectionMatrix();
+ threeRenderer.setRenderTarget(null);
+ composer.render(delta);
+ }
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height
- camera.updateProjectionMatrix()
- threeRenderer.setRenderTarget(null)
- composer.render(delta)
- }
+ loopId = requestAnimationFrame(loop);
+ };
- loopId = requestAnimationFrame(loop)
+ loopId = requestAnimationFrame(loop);
}
- loopId = requestAnimationFrame(loop)
-}
+ function resize() {
+ const w = canvas.width;
+ const h = canvas.height;
-function resize() {
- const w = canvas.width
- const h = canvas.height
+ threeRenderer.setSize(w, h);
+ composer.setSize(w, h);
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("resolution")) {
+ e.uniforms.get("resolution").value.set(w, h);
+ }
+ });
- threeRenderer.setSize(w, h)
- composer.setSize(w, h)
- customEffects.forEach(e => {
- if (e.uniforms.get('resolution')) {
- e.uniforms.get('resolution').value.set(w,h)
- }
- })
-
- if (camera) {
- camera.aspect = w / h
- camera.updateProjectionMatrix()
-}
-}
-//wait until all packages are loaded
-Promise.resolve(load()).then(() => {
-
- class threejsExtension {
- getInfo() {
- return {
- id: "threejsExtension",
- name: "Extra 3D",
- color1: "#222222",
- color2: "#222222",
- color3: "#11cc99",
- menuIconURI,
- blockIconURI: menuIconURI,
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Show Docs", func: "openDocs"},
- {blockType: Scratch.BlockType.BUTTON, text: "Toggle Alerts", func: "alerts"},
- ],
- menus: {}
- }}
- openDocs(){
- open("https://civ3ro.github.io/extensions/Documentation/")
+ if (camera) {
+ camera.aspect = w / h;
+ camera.updateProjectionMatrix();
}
- alerts() {alerts = !alerts; alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!")}
}
- Scratch.extensions.register(new threejsExtension())
-
- class ThreeRenderer {
- getInfo() {
- return {
- id: "threeRenderer",
- name: "Three Renderer",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setRendererRatio", blockType: Scratch.BlockType.COMMAND, text: "set Pixel Ratio to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"}}},
- {opcode: "eulerOrder", blockType: Scratch.BlockType.COMMAND, text: "set euler order to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "YXZ"}}},
- ],
- menus: {}
- }}
-
- setRendererRatio(args) {
- threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE)
- }
- eulerOrder(args) {
- eulerOrder = args.VALUE
- console.log("euler order set to", eulerOrder)
+ //wait until all packages are loaded
+ Promise.resolve(load()).then(() => {
+ class threejsExtension {
+ getInfo() {
+ return {
+ id: "threejsExtension",
+ name: "Extra 3D",
+ color1: "#222222",
+ color2: "#222222",
+ color3: "#11cc99",
+ menuIconURI,
+ blockIconURI: menuIconURI,
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Show Docs",
+ func: "openDocs",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Toggle Alerts",
+ func: "alerts",
+ },
+ ],
+ menus: {},
+ };
+ }
+ openDocs() {
+ open("https://civ3ro.github.io/extensions/Documentation/");
+ }
+ alerts() {
+ alerts = !alerts;
+ alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
+ }
}
+ Scratch.extensions.register(new threejsExtension());
- }
- Scratch.extensions.register(new ThreeRenderer())
+ class ThreeRenderer {
+ getInfo() {
+ return {
+ id: "threeRenderer",
+ name: "Three Renderer",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setRendererRatio",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Pixel Ratio to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "eulerOrder",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set euler order to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "YXZ",
+ },
+ },
+ },
+ ],
+ menus: {},
+ };
+ }
- class ThreeScene {
- constructor() {
- this.THREE = THREE;
- this.scenes = {};
+ setRendererRatio(args) {
+ threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
+ }
+ eulerOrder(args) {
+ eulerOrder = args.VALUE;
+ console.log("euler order set to", eulerOrder);
+ }
}
-
- getInfo() {
- return {
- id: "threeScene",
- name: "Three Scene",
- color1: "#4638c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newScene", blockType: Scratch.BlockType.COMMAND, text: "new Scene [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}}},
-
- {opcode: "setSceneProperty", blockType: Scratch.BlockType.COMMAND, text: "set Scene [PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "sceneProperties", defaultValue: "background"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
+ Scratch.extensions.register(new ThreeRenderer());
+
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
+ getInfo() {
+ return {
+ id: "threeScene",
+ name: "Three Scene",
+ color1: "#4638c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newScene",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new Scene [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ },
+ },
+
+ {
+ opcode: "setSceneProperty",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Scene [PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneProperties",
+ defaultValue: "background",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
"---",
- {opcode: "getSceneObjects", blockType: Scratch.BlockType.REPORTER, text: "get Scene [THING]", arguments:{THING: {type: Scratch.ArgumentType.STRING, menu: "sceneThings"}}},
- {opcode: "reset", blockType: Scratch.BlockType.COMMAND, text: "Reset Everything"}
+ {
+ opcode: "getSceneObjects",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get Scene [THING]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneThings",
+ },
+ },
+ },
+ {
+ opcode: "reset",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Reset Everything",
+ },
],
- menus: {
- sceneProperties: {acceptReporters: false, items: [
- {text: "Background", value: "background"},{text: "Background Blurriness", value: "backgroundBlurriness"},{text: "Background Intensity", value: "backgroundIntensity"},{text: "Background Rotation", value: "backgroundRotation"},
- {text: "Environment", value: "environment"},{text: "Environment Intensity", value: "environmentIntensity"},{text: "Environment Rotation", value: "environmentRotation"},{text: "Fog", value: "fog"},
- ]},
- sceneThings: {acceptReporters: false, items: ["Objects", "Materials", "Geometries","Lights","Scene Properties","Other assets"]},
-
- }
- }}
-
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME
- scene.background = new THREE.Color("#222")
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {...this.scenes, {scene}};
- resetor(0)
- }
+ menus: {
+ sceneProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Background",
+ value: "background",
+ },
+ {
+ text: "Background Blurriness",
+ value: "backgroundBlurriness",
+ },
+ {
+ text: "Background Intensity",
+ value: "backgroundIntensity",
+ },
+ {
+ text: "Background Rotation",
+ value: "backgroundRotation",
+ },
+ {
+ text: "Environment",
+ value: "environment",
+ },
+ {
+ text: "Environment Intensity",
+ value: "environmentIntensity",
+ },
+ {
+ text: "Environment Rotation",
+ value: "environmentRotation",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ ],
+ },
+ sceneThings: {
+ acceptReporters: false,
+ items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
+ },
+ },
+ };
+ }
- reset() {
- resetor(1)
- }
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME;
+ scene.background = new THREE.Color("#222");
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {
+ ...this.scenes,
+ ...scene,
+ };
+ resetor(0);
+ }
+
+ reset() {
+ resetor(1);
+ }
- async setSceneProperty(args) {
+ async setSceneProperty(args) {
const property = args.PROPERTY;
const value = getAsset(args.VALUE);
scene[property] = value;
+ }
+ getSceneObjects(args) {
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse((obj) => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ else if (args.THING === "Scene Properties") {
+ console.log(scene);
+ return "check console";
+ } else if (args.THING === "Other assets") return JSON.stringify(assets);
+
+ return JSON.stringify(names); // if objects
+ }
}
- getSceneObjects(args){
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse(obj => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- }
- else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials))
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries))
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights))
- else if (args.THING === "Scene Properties") {console.log(scene); return "check console"}
- else if (args.THING === "Other assets") return JSON.stringify(assets)
-
- return JSON.stringify(names); // if objects
- }
+ Scratch.extensions.register(new ThreeScene());
- }
- Scratch.extensions.register(new ThreeScene())
-
- class ThreeCameras {
- getInfo() {
- return {
- id: "threeCameras",
- name: "Three Cameras",
- color1: "#38c59bff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addCamera", blockType: Scratch.BlockType.COMMAND, text: "add camera [TYPE] [CAMERA] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "cameraTypes"}}},
- {opcode: "setCamera", blockType: Scratch.BlockType.COMMAND, text: "set camera [PROPERTY] of [CAMERA] to [VALUE]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "0.1", exemptFromNormalization: true}}},
- {opcode: "getCamera", blockType: Scratch.BlockType.REPORTER, text: "get camera [PROPERTY] of [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}}},
+ class ThreeCameras {
+ getInfo() {
+ return {
+ id: "threeCameras",
+ name: "Three Cameras",
+ color1: "#38c59bff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add camera [TYPE] [CAMERA] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraTypes",
+ },
+ },
+ },
+ {
+ opcode: "setCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "0.1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "getCamera",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get camera [PROPERTY] of [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ },
+ },
"---",
- {opcode: "renderSceneCamera", blockType: Scratch.BlockType.COMMAND, text: "set rendering camera to [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}}},
+ {
+ opcode: "renderSceneCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rendering camera to [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ },
+ },
"---",
- {opcode: "cubeCamera", blockType: Scratch.BlockType.COMMAND, text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "cubeCamera"}, GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
+ {
+ opcode: "cubeCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "cubeCamera",
+ },
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
"---",
- {opcode: "renderTarget", blockType: Scratch.BlockType.COMMAND, text: "set a RenderTarget: [RT] for camera [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
- {opcode: "sizeTarget", blockType: Scratch.BlockType.COMMAND, text: "set RenderTarget [RT] size to [W] [H]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, W: {type: Scratch.ArgumentType.NUMBER, defaultValue: 480}, H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 360},} },
- {opcode: "getTarget", blockType: Scratch.BlockType.REPORTER, text: "get RenderTarget: [RT] texture", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
- {opcode: "removeTarget", blockType: Scratch.BlockType.COMMAND, text: "remove RenderTarget: [RT]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
+ {
+ opcode: "renderTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set a RenderTarget: [RT] for camera [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "sizeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set RenderTarget [RT] size to [W] [H]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ W: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 480,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 360,
+ },
+ },
+ },
+ {
+ opcode: "getTarget",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get RenderTarget: [RT] texture",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "removeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove RenderTarget: [RT]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
],
- menus: {
- cameraTypes: {acceptReporters: false, items: [
- {text: "Perspective", value: "PerspectiveCamera"},
- ]},
- cameraProperties: {acceptReporters: false, items: [
- {text: "Near", value: "near"},{text: "Far", value: "far"},{text: "FOV", value: "fov"},{text: "Focus (nothing...)", value: "focus"},{text: "Zoom", value: "zoom"},
- ]},
- }
- }}
- addCamera(args) {
- let v2 = new THREE.Vector2()
- threeRenderer.getSize(v2)
- const object = new THREE.PerspectiveCamera(90, v2.x / v2.y )
- object.position.z = 3
-
- createObject(args.CAMERA, object, args.GROUP)
- }
- setCamera(args) {
- let object = getObject(args.CAMERA)
- object[args.PROPERTY] = args.VALUE
- object.updateProjectionMatrix()
- }
- getCamera(args) {
- let object = getObject(args.CAMERA)
- const value = JSON.stringify(object[args.PROPERTY])
- return value
- }
- renderSceneCamera(args) {
- let object = getObject(args.CAMERA)
- if (!object) return
- camera = object
- //reset composer, else it does not update.
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
+ menus: {
+ cameraTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Perspective",
+ value: "PerspectiveCamera",
+ }, ],
+ },
+ cameraProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Near",
+ value: "near",
+ },
+ {
+ text: "Far",
+ value: "far",
+ },
+ {
+ text: "FOV",
+ value: "fov",
+ },
+ {
+ text: "Focus (nothing...)",
+ value: "focus",
+ },
+ {
+ text: "Zoom",
+ value: "zoom",
+ },
+ ],
+ },
+ },
+ };
+ }
+ addCamera(args) {
+ let v2 = new THREE.Vector2();
+ threeRenderer.getSize(v2);
+ const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
+ object.position.z = 3;
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true } )
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera( 0.1, 500, cubeRenderTarget )
- createObject(args.CAMERA, cubeCamera, args.GROUP)
+ createObject(args.CAMERA, object, args.GROUP);
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA);
+ object[args.PROPERTY] = args.VALUE;
+ object.updateProjectionMatrix();
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA);
+ const value = JSON.stringify(object[args.PROPERTY]);
+ return value;
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA);
+ if (!object) return;
+ camera = object;
+ //reset composer, else it does not update.
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
- renderTargets[args.RT] = {target: cubeRenderTarget, camera: cubeCamera}
- assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture
- }
+ cubeCamera(args) {
+ // Create cube render target
+ const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
+ generateMipmaps: true,
+ });
+ // Create cube camera
+ const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
+ createObject(args.CAMERA, cubeCamera, args.GROUP);
- renderTarget(args) {
- let object = getObject(args.CAMERA)
- const renderTarget = new THREE.WebGLRenderTarget(
- 360,
- 360,
- {
- generateMipmaps: false
- }
- )
+ renderTargets[args.RT] = {
+ target: cubeRenderTarget,
+ camera: cubeCamera,
+ };
+ assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
+ }
- renderTargets[args.RT] = {target: renderTarget, camera: object}
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture
- }
- sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H)
- }
- getTarget(args) {
- const t = renderTargets[args.RT].target.texture
- console.log(t, renderTargets[args.RT])
- return `renderTargets/${t.uuid}`
- }
- removeTarget(args) {
- delete(assets.renderTargets[renderTargets[args.RT].target.texture.uuid])
- renderTargets[args.RT].target.dispose()
- delete(renderTargets[args.RT])
+ renderTarget(args) {
+ let object = getObject(args.CAMERA);
+ const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
+ generateMipmaps: false,
+ });
+
+ renderTargets[args.RT] = {
+ target: renderTarget,
+ camera: object,
+ };
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H);
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture;
+ console.log(t, renderTargets[args.RT]);
+ return `renderTargets/${t.uuid}`;
+ }
+ removeTarget(args) {
+ delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
+ renderTargets[args.RT].target.dispose();
+ delete renderTargets[args.RT];
+ }
}
- }
- Scratch.extensions.register(new ThreeCameras())
-
- class ThreeObjects {
- getInfo() {
- return {
- id: "threeObjects",
- name: "Three Objects",
- color1: "#38c567ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addObject", blockType: Scratch.BlockType.COMMAND, text: "add object [OBJECT3D] [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},TYPE: {type: Scratch.ArgumentType.STRING, menu: "objectTypes"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "cloneObject", blockType: Scratch.BlockType.COMMAND, text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myClone"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ Scratch.extensions.register(new ThreeCameras());
+
+ class ThreeObjects {
+ getInfo() {
+ return {
+ id: "threeObjects",
+ name: "Three Objects",
+ color1: "#38c567ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add object [OBJECT3D] [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "cloneObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myClone",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "setObject", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of object [OBJECT3D] to [NAME]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "getObject", blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of object [OBJECT3D]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}}},
- {opcode: "objectE", blockType: Scratch.BlockType.BOOLEAN, text: "is there an object [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ opcode: "setObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "getObject",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of object [OBJECT3D]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ },
+ },
+ {
+ opcode: "objectE",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there an object [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "removeObject", blockType: Scratch.BlockType.COMMAND, text: "remove object [OBJECT3D] from scene", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: " ↳ Transforms"},
- {opcode: "setObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ {
+ opcode: "removeObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove object [OBJECT3D] from scene",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: " ↳ Transforms",
+ },
+ {
+ opcode: "setObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
//{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
//{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of [OBJECT3D]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Materials"},
- {opcode: "newMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "new material [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "materialTypes", defaultValue: "MeshStandardMaterial"}}},
- {opcode: "materialE",extensions: ["colours_looks"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a material [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "removeMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "remove material [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "setMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [PROPERTY] of [NAME] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "materialProperties", defaultValue: "color"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
- {opcode: "setBlending",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] blending to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "blendModes"}}},
- {opcode: "setDepth",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] depth to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "depthModes"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Geometries"},
- {opcode: "newGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new geometry [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "geometryTypes", defaultValue: "BoxGeometry"}}},
- {opcode: "geometryE",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a geometry [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "removeGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "remove geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
+ {
+ opcode: "getObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of [OBJECT3D]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Materials",
+ },
+ {
+ opcode: "newMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new material [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialTypes",
+ defaultValue: "MeshStandardMaterial",
+ },
+ },
+ },
+ {
+ opcode: "materialE",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a material [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "removeMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove material [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "setMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [PROPERTY] of [NAME] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialProperties",
+ defaultValue: "color",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "setBlending",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] blending to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ },
+ },
+ },
+ {
+ opcode: "setDepth",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] depth to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "depthModes",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Geometries",
+ },
+ {
+ opcode: "newGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new geometry [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "geometryTypes",
+ defaultValue: "BoxGeometry",
+ },
+ },
+ },
+ {
+ opcode: "geometryE",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a geometry [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "removeGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
"---",
- {opcode: "newGeo",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new empty geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoPoints",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] vertex points to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoUVs",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] UVs to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[UVs]"}}},
+ {
+ opcode: "newGeo",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new empty geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoPoints",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] vertex points to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoUVs",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] UVs to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[UVs]",
+ },
+ },
+ },
"---",
- {opcode: "splines", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create spline [NAME] from curve [CURVE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}}},
- {opcode: "splineModel", extensions: ["colours_operators"], blockType: Scratch.BlockType.COMMAND, text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, MODEL: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}, SPACING: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
+ {
+ opcode: "splines",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create spline [NAME] from curve [CURVE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "splineModel",
+ extensions: ["colours_operators"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ MODEL: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ SPACING: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
"---",
- {blockType: Scratch.BlockType.BUTTON, text: "Convert font to JSON", func: "openConv"},
- {blockType: Scratch.BlockType.BUTTON, text: "Load JSON font file", func: "loadFont"},
- {opcode: "text", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myText"}, TEXT: {type: Scratch.ArgumentType.STRING, defaultValue: "C-369"}, FONT: {type: Scratch.ArgumentType.STRING, menu: "fonts"}, S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, D: {type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1}, CS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 6}}},
- ],
- menus: {
- objectVector3: {acceptReporters: false, items: [
- {text: "Positon", value: "position"},{text: "Rotation", value: "rotation"},{text: "Scale", value: "scale"},{text: "Facing Direction (.up)", value: "up"}
- ]},
- objectProperties: {acceptReporters: false, items: [
- {text: "Geometry", value: "geometry"},{text: "Material", value: "material"},{text: "Visible (true/false)", value: "visible"},
- ]},
- objectTypes: { acceptReporters: false, items: [
- { text: "Mesh", value: "Mesh" }, { text: "Sprite", value: "Sprite" }, { text: "Points", value: "Points" }, { text: "Line", value: "Line" }, { text: "Group", value: "Group" }
- ]},
- XYZ: {acceptReporters: false, items: [{text: "X", value: "x"},{text: "Y", value: "y"},{text: "Z", value: "z"}]},
- materialProperties: {acceptReporters: false, items: [
- "|GENERAL| <-- not a property",
- { text: "Color", value: "color" },
- { text: "Map", value: "map" },
- { text: "Opacity", value: "opacity" },
- { text: "Transparent", value: "transparent" },
- { text: "Alpha Map", value: "alphaMap" },
- { text: "Alpha Test", value: "alphaTest" },
- { text: "Depth Test", value: "depthTest" },
- { text: "Depth Write", value: "depthWrite" },
- { text: "Color Write", value: "colorWrite" },
- { text: "Side", value: "side" },
- { text: "Visible", value: "visible" },/*
- { text: "Blending", value: "blending" },
- { text: "Blend Src", value: "blendSrc" },
- { text: "Blend Dst", value: "blendDst" },
- { text: "Blend Equation", value: "blendEquation" },
- { text: "Blend Src Alpha", value: "blendSrcAlpha" },
- { text: "Blend Dst Alpha", value: "blendDstAlpha" },
- { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
- { text: "Blend Aplha", value: "blendAplha" },
- { text: "Blend Color", value: "blendColor" },
- { text: "Alpha Hash", value: "alphaHash" },
- { text: "Premultiplied Alpha", value: "premultipliedAlpha" },
-
- { text: "Tone Mapped", value: "toneMapped" },
- { text: "Fog", value: "fog" },
- { text: "Flat Shading", value: "flatShading" },
-
- "|MESH Standard / Physical| <-- not a property",
- { text: "Metalness", value: "metalness" },
- { text: "Metalness Map", value: "metalnessMap" },
- { text: "Roughness", value: "roughness" },
- { text: "Reflectivity", value: "reflectivity" },
- { text: "Roughness Map", value: "roughnessMap" },
- { text: "Emissive", value: "emissive" },
- { text: "Emissive Intensity", value: "emissiveIntensity" },
- { text: "Emissive Map", value: "emissiveMap" },
- { text: "Env Map", value: "envMap" },
- { text: "Env Map Intensity", value: "envMapIntensity" },
- { text: "Env Map Rotation", value: "envMapRotation" },
- { text: "Ior", value: "ior" },
- { text: "Refraction Ratio", value: "refractionRatio" },
- { text: "Clearcoat", value: "clearcoat" },
- { text: "Clearcoat Map", value: "clearcoatMap" },
- { text: "Clearcoat Roughness", value: "clearcoatRoughness" },
- { text: "Clearcoat Roughness Map", value: "clearcoatRoughnessMap" },
- { text: "Dispersion", value: "dispersion" },
- { text: "Sheen", value: "sheen" },
- { text: "Sheen Color", value: "sheenColor" },
- { text: "Sheen Color Map", value: "sheenColorMap" },
- { text: "Sheen Roughness", value: "sheenRoughness" },
- { text: "Sheen Roughness Map", value: "sheenRoughnessMap" },
- { text: "Specular Color", value: "specularColor" },
- { text: "Specular Color Map", value: "specularColorMap" },
- { text: "Specular Intensity", value: "specularIntensity" },
- { text: "Specular Intensity Map", value: "specularIntensityMap" },
- { text: "Transmission", value: "transmission" },
- { text: "Transmission Map", value: "transmissionMap" },
- { text: "Thickness", value: "thickness" },
- { text: "Thickness Map", value: "thicknessMap" },
- { text: "Anisotropy", value: "anisotropy" },
- { text: "Anisotropy Map", value: "anisotropyMap" },
- { text: "Anisotropy Rotation", value: "anisotropyRotation" },
- { text: "Attenuation Distance", value: "attenuationDistance" },
- { text: "Attenuation Color", value: "attenuationColor" },
- { text: "Thickness", value: "thickness" },
- { text: "Iridescence", value: "iridescence" },
- { text: "Iridescence Ior", value: "iridescenceIOR" },
- { text: "Iridescence Map", value: "iridescenceMap" },
- { text: "Iridescence Thickness Range", value: "iridescenceThicknessRange" },
-
- "|MESH Displacement / Normal / Bump| <-- not a property",
- { text: "Displacement Map", value: "displacementMap" },
- { text: "Displacement Scale", value: "displacementScale" },
- { text: "Displacement Bias", value: "displacementBias" },
- { text: "Bump Map", value: "bumpMap" },
- { text: "Bump Scale", value: "bumpScale" },
- { text: "Normal Map Type", value: "normalMapType" },
-
- "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
- { text: "Shininess", value: "shininess" },
-
- { text: "Wireframe", value: "wireframe" },
- { text: "Wireframe Linewidth", value: "wireframeLinewidth" },
- { text: "Wireframe Linecap", value: "wireframeLinecap" },
- { text: "Wireframe Linejoin", value: "wireframeLinejoin" },
-
- "|POINTS| <-- not a property",
- { text: "Size", value: "size" },
- { text: "Size Attenuation", value: "sizeAttenuation" },
-
- "|LINES| <-- not a property",
- { text: "Scale", value: "scale" },
- { text: "Dash Size", value: "dashSize" },
- { text: "Gap Size", value: "gapSize" },
-
- "|SPRITES| <-- not a property",
- { text: "Rotation", value: "rotation" }
-]},
- blendModes: {acceptReporters: false, items: [
- { text: "No Blending", value: "NoBlending" },{ text: "Normal Blending", value: "NormalBlending" },{ text: "Additive Blending", value: "AdditiveBlending" },{ text: "Subtractive Blending", value: "SubtractiveBlending" },{ text: "Multiply Blending", value: "MultiplyBlending" },{ text: "Custom Blending", value: "CustomBlending" }
- ]},
- depthModes: {acceptReporters: false, items: [
- { text: "Never Depth", value: "NeverDepth" },{ text: "Always Depth", value: "AlwaysDepth" },{ text: "Equal Depth", value: "EqualDepth" },{ text: "Less Depth", value: "LessDepth" },{ text: "Less Equal Depth", value: "LessEqualDepth" },{ text: "Greater Equal Depth", value: "GreaterEqualDepth" },{ text: "Greater Depth", value: "GreaterDepth" },{ text: "Not Equal Depth", value: "NotEqualDepth" }
- ]},
- materialTypes:{acceptReporters: false, items: [
- {text:"Mesh Basic Material",value:"MeshBasicMaterial"},{text:"Mesh Standard Material",value:"MeshStandardMaterial"},{text:"Mesh Physical Material",value:"MeshPhysicalMaterial"},{text:"Mesh Lambert Material",value:"MeshLambertMaterial"},{text:"Mesh Phong Material",value:"MeshPhongMaterial"},{text:"Mesh Depth Material",value:"MeshDepthMaterial"},{text:"Mesh Normal Material",value:"MeshNormalMaterial"},{text:"Mesh Matcap Material",value:"MeshMatcapMaterial"},{text:"Mesh Toon Material",value:"MeshToonMaterial"},{text:"Line Basic Material",value:"LineBasicMaterial"},{text:"Line Dashed Material",value:"LineDashedMaterial"},{text:"Points Material",value:"PointsMaterial"},{text:"Sprite Material",value:"SpriteMaterial"},{text:"Shadow Material",value:"ShadowMaterial"}
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- geometryTypes: {acceptReporters: false, items: [
- {text: "Box Geometry", value: "BoxGeometry"},{text: "Sphere Geometry", value: "SphereGeometry"},{text: "Cylinder Geometry", value: "CylinderGeometry"},{text: "Plane Geometry", value: "PlaneGeometry"},{text: "Circle Geometry", value: "CircleGeometry"},{text: "Torus Geometry", value: "TorusGeometry"},{text: "Torus Knot Geometry", value: "TorusKnotGeometry"},
- ]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Convert font to JSON",
+ func: "openConv",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load JSON font file",
+ func: "loadFont",
+ },
+ {
+ opcode: "text",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myText",
+ },
+ TEXT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "C-369",
+ },
+ FONT: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "fonts",
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ D: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ CS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 6,
+ },
+ },
+ },
+ ],
+ menus: {
+ objectVector3: {
+ acceptReporters: false,
+ items: [{
+ text: "Positon",
+ value: "position",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Facing Direction (.up)",
+ value: "up",
+ },
+ ],
+ },
+ objectProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Geometry",
+ value: "geometry",
+ },
+ {
+ text: "Material",
+ value: "material",
+ },
+ {
+ text: "Visible (true/false)",
+ value: "visible",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh",
+ value: "Mesh",
+ },
+ {
+ text: "Sprite",
+ value: "Sprite",
+ },
+ {
+ text: "Points",
+ value: "Points",
+ },
+ {
+ text: "Line",
+ value: "Line",
+ },
+ {
+ text: "Group",
+ value: "Group",
+ },
+ ],
+ },
+ XYZ: {
+ acceptReporters: false,
+ items: [{
+ text: "X",
+ value: "x",
+ },
+ {
+ text: "Y",
+ value: "y",
+ },
+ {
+ text: "Z",
+ value: "z",
+ },
+ ],
+ },
+ materialProperties: {
+ acceptReporters: false,
+ items: [
+ "|GENERAL| <-- not a property",
+ {
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map",
+ value: "map",
+ },
+ {
+ text: "Opacity",
+ value: "opacity",
+ },
+ {
+ text: "Transparent",
+ value: "transparent",
+ },
+ {
+ text: "Alpha Map",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test",
+ value: "alphaTest",
+ },
+ {
+ text: "Depth Test",
+ value: "depthTest",
+ },
+ {
+ text: "Depth Write",
+ value: "depthWrite",
+ },
+ {
+ text: "Color Write",
+ value: "colorWrite",
+ },
+ {
+ text: "Side",
+ value: "side",
+ },
+ {
+ text: "Visible",
+ value: "visible",
+ },
+ /*
+ { text: "Blending", value: "blending" },
+ { text: "Blend Src", value: "blendSrc" },
+ { text: "Blend Dst", value: "blendDst" },
+ { text: "Blend Equation", value: "blendEquation" },
+ { text: "Blend Src Alpha", value: "blendSrcAlpha" },
+ { text: "Blend Dst Alpha", value: "blendDstAlpha" },
+ { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
+ {
+ text: "Blend Aplha",
+ value: "blendAplha",
+ },
+ {
+ text: "Blend Color",
+ value: "blendColor",
+ },
+ {
+ text: "Alpha Hash",
+ value: "alphaHash",
+ },
+ {
+ text: "Premultiplied Alpha",
+ value: "premultipliedAlpha",
+ },
+
+ {
+ text: "Tone Mapped",
+ value: "toneMapped",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ {
+ text: "Flat Shading",
+ value: "flatShading",
+ },
+
+ "|MESH Standard / Physical| <-- not a property",
+ {
+ text: "Metalness",
+ value: "metalness",
+ },
+ {
+ text: "Metalness Map",
+ value: "metalnessMap",
+ },
+ {
+ text: "Roughness",
+ value: "roughness",
+ },
+ {
+ text: "Reflectivity",
+ value: "reflectivity",
+ },
+ {
+ text: "Roughness Map",
+ value: "roughnessMap",
+ },
+ {
+ text: "Emissive",
+ value: "emissive",
+ },
+ {
+ text: "Emissive Intensity",
+ value: "emissiveIntensity",
+ },
+ {
+ text: "Emissive Map",
+ value: "emissiveMap",
+ },
+ {
+ text: "Env Map",
+ value: "envMap",
+ },
+ {
+ text: "Env Map Intensity",
+ value: "envMapIntensity",
+ },
+ {
+ text: "Env Map Rotation",
+ value: "envMapRotation",
+ },
+ {
+ text: "Ior",
+ value: "ior",
+ },
+ {
+ text: "Refraction Ratio",
+ value: "refractionRatio",
+ },
+ {
+ text: "Clearcoat",
+ value: "clearcoat",
+ },
+ {
+ text: "Clearcoat Map",
+ value: "clearcoatMap",
+ },
+ {
+ text: "Clearcoat Roughness",
+ value: "clearcoatRoughness",
+ },
+ {
+ text: "Clearcoat Roughness Map",
+ value: "clearcoatRoughnessMap",
+ },
+ {
+ text: "Dispersion",
+ value: "dispersion",
+ },
+ {
+ text: "Sheen",
+ value: "sheen",
+ },
+ {
+ text: "Sheen Color",
+ value: "sheenColor",
+ },
+ {
+ text: "Sheen Color Map",
+ value: "sheenColorMap",
+ },
+ {
+ text: "Sheen Roughness",
+ value: "sheenRoughness",
+ },
+ {
+ text: "Sheen Roughness Map",
+ value: "sheenRoughnessMap",
+ },
+ {
+ text: "Specular Color",
+ value: "specularColor",
+ },
+ {
+ text: "Specular Color Map",
+ value: "specularColorMap",
+ },
+ {
+ text: "Specular Intensity",
+ value: "specularIntensity",
+ },
+ {
+ text: "Specular Intensity Map",
+ value: "specularIntensityMap",
+ },
+ {
+ text: "Transmission",
+ value: "transmission",
+ },
+ {
+ text: "Transmission Map",
+ value: "transmissionMap",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Thickness Map",
+ value: "thicknessMap",
+ },
+ {
+ text: "Anisotropy",
+ value: "anisotropy",
+ },
+ {
+ text: "Anisotropy Map",
+ value: "anisotropyMap",
+ },
+ {
+ text: "Anisotropy Rotation",
+ value: "anisotropyRotation",
+ },
+ {
+ text: "Attenuation Distance",
+ value: "attenuationDistance",
+ },
+ {
+ text: "Attenuation Color",
+ value: "attenuationColor",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Iridescence",
+ value: "iridescence",
+ },
+ {
+ text: "Iridescence Ior",
+ value: "iridescenceIOR",
+ },
+ {
+ text: "Iridescence Map",
+ value: "iridescenceMap",
+ },
+ {
+ text: "Iridescence Thickness Range",
+ value: "iridescenceThicknessRange",
+ },
+
+ "|MESH Displacement / Normal / Bump| <-- not a property",
+ {
+ text: "Displacement Map",
+ value: "displacementMap",
+ },
+ {
+ text: "Displacement Scale",
+ value: "displacementScale",
+ },
+ {
+ text: "Displacement Bias",
+ value: "displacementBias",
+ },
+ {
+ text: "Bump Map",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ {
+ text: "Normal Map Type",
+ value: "normalMapType",
+ },
+
+ "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
+ {
+ text: "Shininess",
+ value: "shininess",
+ },
+
+ {
+ text: "Wireframe",
+ value: "wireframe",
+ },
+ {
+ text: "Wireframe Linewidth",
+ value: "wireframeLinewidth",
+ },
+ {
+ text: "Wireframe Linecap",
+ value: "wireframeLinecap",
+ },
+ {
+ text: "Wireframe Linejoin",
+ value: "wireframeLinejoin",
+ },
+
+ "|POINTS| <-- not a property",
+ {
+ text: "Size",
+ value: "size",
+ },
+ {
+ text: "Size Attenuation",
+ value: "sizeAttenuation",
+ },
+
+ "|LINES| <-- not a property",
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Dash Size",
+ value: "dashSize",
+ },
+ {
+ text: "Gap Size",
+ value: "gapSize",
+ },
+
+ "|SPRITES| <-- not a property",
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [{
+ text: "No Blending",
+ value: "NoBlending",
+ },
+ {
+ text: "Normal Blending",
+ value: "NormalBlending",
+ },
+ {
+ text: "Additive Blending",
+ value: "AdditiveBlending",
+ },
+ {
+ text: "Subtractive Blending",
+ value: "SubtractiveBlending",
+ },
+ {
+ text: "Multiply Blending",
+ value: "MultiplyBlending",
+ },
+ {
+ text: "Custom Blending",
+ value: "CustomBlending",
+ },
+ ],
+ },
+ depthModes: {
+ acceptReporters: false,
+ items: [{
+ text: "Never Depth",
+ value: "NeverDepth",
+ },
+ {
+ text: "Always Depth",
+ value: "AlwaysDepth",
+ },
+ {
+ text: "Equal Depth",
+ value: "EqualDepth",
+ },
+ {
+ text: "Less Depth",
+ value: "LessDepth",
+ },
+ {
+ text: "Less Equal Depth",
+ value: "LessEqualDepth",
+ },
+ {
+ text: "Greater Equal Depth",
+ value: "GreaterEqualDepth",
+ },
+ {
+ text: "Greater Depth",
+ value: "GreaterDepth",
+ },
+ {
+ text: "Not Equal Depth",
+ value: "NotEqualDepth",
+ },
+ ],
+ },
+ materialTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh Basic Material",
+ value: "MeshBasicMaterial",
+ },
+ {
+ text: "Mesh Standard Material",
+ value: "MeshStandardMaterial",
+ },
+ {
+ text: "Mesh Physical Material",
+ value: "MeshPhysicalMaterial",
+ },
+ {
+ text: "Mesh Lambert Material",
+ value: "MeshLambertMaterial",
+ },
+ {
+ text: "Mesh Phong Material",
+ value: "MeshPhongMaterial",
+ },
+ {
+ text: "Mesh Depth Material",
+ value: "MeshDepthMaterial",
+ },
+ {
+ text: "Mesh Normal Material",
+ value: "MeshNormalMaterial",
+ },
+ {
+ text: "Mesh Matcap Material",
+ value: "MeshMatcapMaterial",
+ },
+ {
+ text: "Mesh Toon Material",
+ value: "MeshToonMaterial",
+ },
+ {
+ text: "Line Basic Material",
+ value: "LineBasicMaterial",
+ },
+ {
+ text: "Line Dashed Material",
+ value: "LineDashedMaterial",
+ },
+ {
+ text: "Points Material",
+ value: "PointsMaterial",
+ },
+ {
+ text: "Sprite Material",
+ value: "SpriteMaterial",
+ },
+ {
+ text: "Shadow Material",
+ value: "ShadowMaterial",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ geometryTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box Geometry",
+ value: "BoxGeometry",
+ },
+ {
+ text: "Sphere Geometry",
+ value: "SphereGeometry",
+ },
+ {
+ text: "Cylinder Geometry",
+ value: "CylinderGeometry",
+ },
+ {
+ text: "Plane Geometry",
+ value: "PlaneGeometry",
+ },
+ {
+ text: "Circle Geometry",
+ value: "CircleGeometry",
+ },
+ {
+ text: "Torus Geometry",
+ value: "TorusGeometry",
+ },
+ {
+ text: "Torus Knot Geometry",
+ value: "TorusKnotGeometry",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
// @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model! (GLB Loader category)"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- fonts: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model! (GLB Loader category)"]
+ ];
// @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.json'))
- if (models.length < 1) return [["Load a font!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
+ return models.map((m) => [m.name]);
+ },
+ },
+ fonts: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
- }
- }}
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".json"));
+ if (models.length < 1) return [
+ ["Load a font!"]
+ ];
- addObject(args) {
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ addObject(args) {
const object = new THREE[args.TYPE]();
- object.castShadow = true
- object.receiveShadow = true
+ object.castShadow = true;
+ object.receiveShadow = true;
- createObject(args.OBJECT3D, object, args.GROUP)
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D)
- const clone = object.clone(true)
- clone.name
- createObject(args.NAME, clone, args.GROUP)
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
+ createObject(args.OBJECT3D, object, args.GROUP);
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D);
+ const clone = object.clone(true);
+ clone.name;
+ createObject(args.NAME, clone, args.GROUP);
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ let values = JSON.parse(args.VALUE);
function degToRad(deg) {
- return deg * Math.PI / 180;
+ return (deg * Math.PI) / 180;
}
-
if (object.rigidBody) {
- const x = values[0]
- const y = values[1]
- const z = values[2]
+ const x = values[0];
+ const y = values[1];
+ const z = values[2];
if (args.PROPERTY === "rotation") {
- const euler = new THREE.Euler(
- degToRad(x),
- degToRad(y),
- degToRad(z),
- 'YXZ'
- )
- const quaternion = new THREE.Quaternion()
- quaternion.setFromEuler(euler)
+ const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
+ const quaternion = new THREE.Quaternion();
+ quaternion.setFromEuler(euler);
object.rigidBody.setRotation({
x: quaternion.x,
y: quaternion.y,
z: quaternion.z,
- w: quaternion.w
+ w: quaternion.w,
});
} else if (args.PROPERTY === "position") {
- object.rigidBody.setTranslation({ x: x, y: y, z: z }, true)
+ object.rigidBody.setTranslation({
+ x: x,
+ y: y,
+ z: z,
+ },
+ true
+ );
}
- return
+ return;
}
- if (object.isCamera == true && controls) {
-
- }
+ if (object.isCamera == true && controls) {}
if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.set(0,0,0)
+ values = values.map((v) => (v * Math.PI) / 180);
+ object.rotation.set(0, 0, 0);
+ }
+ if (object.isDirectionalLight == true) {
+ object.pos = new THREE.Vector3(...values);
+ console.log(true, values, object.pos);
+ return;
}
- if (object.isDirectionalLight == true) {object.pos = new THREE.Vector3(...values); console.log(true, values, object.pos); return}
- object[args.PROPERTY].set(...values);
+ object[args.PROPERTY].set(...values);
- if (object.type == "CubeCamera") object.updateCoordinateSystem()
- }
- /*
- changeObjectV3(args) {
- getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
+ if (object.type == "CubeCamera") object.updateCoordinateSystem();
+ }
+ /*
+ changeObjectV3(args) {
+ getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.x += values[0]
- object.rotation.y += values[1]
- object.rotation.z += values[2]
- }
- else {
- object[args.PROPERTY].add(...values);
- }
- }
- changeObjectXV3(args) {
- getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "rotation") value = value * Math.PI / 180
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.x += values[0]
+ object.rotation.y += values[1]
+ object.rotation.z += values[2]
+ }
+ else {
+ object[args.PROPERTY].add(...values);
+ }
+ }
+ changeObjectXV3(args) {
+ getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "rotation") value = value * Math.PI / 180
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let values = vector3ToString(object[args.PROPERTY])
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let values = vector3ToString(object[args.PROPERTY]);
if (args.PROPERTY === "rotation") {
- const toDeg = Math.PI/180
- values = [values[0]/toDeg,values[1]/toDeg,values[2]/toDeg,]
+ const toDeg = Math.PI / 180;
+ values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
}
- return JSON.stringify(values)
- }
- setObject(args){
- let object = getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "material") {const mat = materials[args.NAME]; if (mat) value = mat; else value = undefined}
- else if (args.PROPERTY === "geometry") {const geo = geometries[args.NAME]; if (geo) value = geo; else value = undefined}
- else value = !!value
-
- if (value == undefined) return //invalid geo/mat
- object[args.PROPERTY] = value
- }
- getObject(args){
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let value
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value
- }
- removeObject(args) {
- removeObject(args.OBJECT3D)
- }
- objectE(args) {
- return scene.children.map(o => o.name).includes(args.NAME)
- }
+ return JSON.stringify(values);
+ }
+ setObject(args) {
+ let object = getObject(args.OBJECT3D);
+ let value = args.VALUE;
+ if (args.PROPERTY === "material") {
+ const mat = materials[args.NAME];
+ if (mat) value = mat;
+ else value = undefined;
+ } else if (args.PROPERTY === "geometry") {
+ const geo = geometries[args.NAME];
+ if (geo) value = geo;
+ else value = undefined;
+ } else value = !!value;
+
+ if (value == undefined) return; //invalid geo/mat
+ object[args.PROPERTY] = value;
+ }
+ getObject(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let value;
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value;
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D);
+ }
+ objectE(args) {
+ return scene.children.map((o) => o.name).includes(args.NAME);
+ }
-//defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert ("material already exists! will replace...")
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
+ //defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof(args.VALUE) == "string" && args.VALUE.at(0) == "|") return
- const mat = materials[args.NAME]
-
- let value = args.VALUE
-
- if (args.VALUE == "false") value = false
-
- if (args.PROPERTY == "side") {value = (args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide)}
- else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE))
- else value = getAsset(value)
-
-
- console.log("o:", args.VALUE, typeof(args.VALUE))
- console.log("r:", value, typeof(value))
-
- mat[args.PROPERTY] = await (value) //await incase its a texture
- mat.needsUpdate = true
- }
- setBlending(args) {
- const mat = materials[args.NAME]
- mat.blending = THREE[args.VALUE]
- mat.premultipliedAlpha = true
- mat.needsUpdate = true
- }
- setDepth(args) {
- const mat = materials[args.NAME]
- mat.depthFunc = THREE[args.VALUE]
- mat.needsUpdate = true
- }
- removeMaterial(args){
- const mat = materials[args.NAME]
- mat.dispose()
- delete(materials[args.NAME])
- }
- materialE(args) {
- return materials[args.NAME] ? true : false
- }
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
+ const mat = materials[args.NAME];
- newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert ("geometry already exists! will replace...")
- const geo = new THREE[args.TYPE]()
- geo.name = args.NAME
+ let value = args.VALUE;
- geometries[args.NAME] = geo
- }
- setGeometry(args) {
- const geo = geometries[args.NAME]
- geo[args.PROPERTY] = (args.VALUE)
+ if (args.VALUE == "false") value = false;
- geo.needsUpdate = true;
- }
- removeGeometry(args){
- const geo = geometries[args.NAME]
- geo.dispose()
- delete(geometries[args.NAME])
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false
- }
+ if (args.PROPERTY == "side") {
+ value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
+ } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
+ else value = getAsset(value);
- newGeo(args) {
- const geometry = new THREE.BufferGeometry()
- geometry.name = args.NAME
- geometries[args.NAME] = geometry
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME]
- const positions = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v3 of each vertex of each triangle
+ console.log("o:", args.VALUE, typeof args.VALUE);
+ console.log("r:", value, typeof value);
- geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
- geometry.computeVertexNormals()
+ mat[args.PROPERTY] = await value; //await incase its a texture
+ mat.needsUpdate = true;
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME];
+ mat.blending = THREE[args.VALUE];
+ mat.premultipliedAlpha = true;
+ mat.needsUpdate = true;
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME];
+ mat.depthFunc = THREE[args.VALUE];
+ mat.needsUpdate = true;
+ }
+ removeMaterial(args) {
+ const mat = materials[args.NAME];
+ mat.dispose();
+ delete materials[args.NAME];
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false;
+ }
- geometry.needsUpdate = true
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME]
- const UVs = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v2 of each UV of each triangle
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ const geo = new THREE[args.TYPE]();
+ geo.name = args.NAME;
- geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(UVs), 2))
- geometry.needsUpdate = true
- }
+ geometries[args.NAME] = geo;
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo[args.PROPERTY] = args.VALUE;
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE))
- geometry.name = args.NAME
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo.dispose();
+ delete geometries[args.NAME];
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false;
+ }
- geometries[args.NAME] = geometry
- }
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry();
+ geometry.name = args.NAME;
+ geometries[args.NAME] = geometry;
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME];
+ const positions = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v3 of each vertex of each triangle
- async splineModel(args) {
- const model = await getModel(args.MODEL, args.NAME)
- if (!model) return console.warn("Model not found:", args.MODEL)
+ geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
+ geometry.computeVertexNormals();
- const curve = getAsset(args.CURVE)
- const spacing = parseFloat(args.SPACING) || 1
- const curveLength = curve.getLength()
- const divisions = Math.floor(curveLength / spacing)
+ geometry.needsUpdate = true;
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME];
+ const UVs = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v2 of each UV of each triangle
+
+ geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
+ geometry.needsUpdate = true;
+ }
+
+ splines(args) {
+ const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
+ geometry.name = args.NAME;
- const geomList = []
- const matList = []
+ geometries[args.NAME] = geometry;
+ }
- for (let i = 0; i <= divisions; i++) {
- const t = i / divisions
- const pos = curve.getPointAt(t)
- const tangent = curve.getTangentAt(t)
+ async splineModel(args) {
+ const model = await getModel(args.MODEL, args.NAME);
+ if (!model) return console.warn("Model not found:", args.MODEL);
- const temp = model.clone(true)
- temp.position.copy(pos)
+ const curve = getAsset(args.CURVE);
+ const spacing = parseFloat(args.SPACING) || 1;
+ const curveLength = curve.getLength();
+ const divisions = Math.floor(curveLength / spacing);
- const up = new THREE.Vector3(0, 0, 1)
- const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize())
- temp.quaternion.copy(quat)
+ const geomList = [];
+ const matList = [];
- temp.updateMatrixWorld(true)
+ for (let i = 0; i <= divisions; i++) {
+ const t = i / divisions;
+ const pos = curve.getPointAt(t);
+ const tangent = curve.getTangentAt(t);
+
+ const temp = model.clone(true);
+ temp.position.copy(pos);
+
+ const up = new THREE.Vector3(0, 0, 1);
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
+ temp.quaternion.copy(quat);
+
+ temp.updateMatrixWorld(true);
+
+ temp.traverse((child) => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone();
+ geom.applyMatrix4(child.matrixWorld);
+ geomList.push(geom);
+ matList.push(child.material); //.clone() ?
+ }
+ });
+ }
- temp.traverse(child => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone()
- geom.applyMatrix4(child.matrixWorld)
- geomList.push(geom)
- matList.push(child.material) //.clone() ?
+ const validGeoms = geomList.filter((g) => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
+ if (!ok) console.warn("geometry skipped:", g);
+ return ok;
+ });
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
+ merged.computeBoundingBox();
+ merged.computeBoundingSphere();
+
+ merged.name = args.NAME;
+ geometries[args.NAME] = merged;
+ matList.name = args.NAME;
+ materials[args.NAME] = matList;
}
- })
- }
- const validGeoms = geomList.filter(g => {
- const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position
- if (!ok) console.warn("geometry skipped:", g)
- return ok
- })
+ async text(args) {
+ const fontFile = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === args.FONT);
+ if (!fontFile) return;
- const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true)
- merged.computeBoundingBox()
- merged.computeBoundingSphere()
+ const json = new TextDecoder().decode(fontFile.asset.data.buffer);
+ const fontData = JSON.parse(json);
- merged.name = args.NAME
- geometries[args.NAME] = merged
- matList.name = args.NAME
- materials[args.NAME] = matList
- }
-
- async text(args) {
- const fontFile = runtime.getTargetForStage().getSounds().find(c => c.name === args.FONT)
- if (!fontFile) return
+ const font = fontLoad.parse(fontData);
- const json = new TextDecoder().decode(fontFile.asset.data.buffer)
- const fontData = JSON.parse(json)
+ const params = {
+ font: font,
+ size: JSON.parse(args.S),
+ height: JSON.parse(args.D),
+ curveSegments: JSON.parse(args.CS),
+ bevelEnabled: false,
+ };
+ const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
+ geometry.computeVertexNormals();
+ geometry.center(); // optional, recenters the text
- const font = fontLoad.parse(fontData)
+ geometry.name = args.NAME;
- const params = {font: font, size: JSON.parse(args.S), height: JSON.parse(args.D), curveSegments: JSON.parse(args.CS), bevelEnabled: false}
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params)
- geometry.computeVertexNormals()
- geometry.center() // optional, recenters the text
-
+ geometries[args.NAME] = geometry;
+ }
- geometry.name = args.NAME
+ async loadFont() {
+ openFileExplorer(".json").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
- geometries[args.NAME] = geometry
- }
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
- async loadFont() {
- openFileExplorer(".json").then(files => {
- const file = files[0]
- const reader = new FileReader()
+ // From lily's assets
+ // // Thank you PenguinMod for providing this code.
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result
-
- // From lily's assets
- // // Thank you PenguinMod for providing this code.
-
- const targetId = runtime.getTargetForStage().id //util.target.id not working!
- const assetName = Cast.toString(file.name)
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
- const buffer = arrayBuffer
+ const buffer = arrayBuffer;
- const storage = runtime.storage
+ const storage = runtime.storage;
const asset = storage.createAsset(
storage.AssetType.Sound,
storage.DataFormat.MP3,
@@ -1329,7 +2519,7 @@ Promise.resolve(load()).then(() => {
new Uint8Array(buffer),
null,
true
- )
+ );
try {
await vm.addSound(
@@ -1340,615 +2530,1386 @@ Promise.resolve(load()).then(() => {
name: assetName,
},
targetId
- )
- alert("Font loaded successfully!")
+ );
+ alert("Font loaded successfully!");
} catch (e) {
- console.error(e)
- alert("Error loading font.")
+ console.error(e);
+ alert("Error loading font.");
}
-
- // End of PenguinMod
+
+ // End of PenguinMod
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ openConv() {
+ {
+ open("https://gero3.github.io/facetype.js/");
}
+ }
+ }
+ Scratch.extensions.register(new ThreeObjects());
+
+ class ThreeLights {
+ getInfo() {
+ return {
+ id: "threeLights",
+ name: "Three Lights",
+ color1: "#c7a22aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add light [NAME] type [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightTypes",
+ },
+ },
+ },
+ {
+ opcode: "setLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set light [NAME][PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightProperties",
+ defaultValue: "intensity",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ lightTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Ambient Light",
+ value: "AmbientLight",
+ },
+ {
+ text: "Directional Light",
+ value: "DirectionalLight",
+ },
+ {
+ text: "Point Light",
+ value: "PointLight",
+ },
+ {
+ text: "Hemisphere Light",
+ value: "HemisphereLight",
+ },
+ {
+ text: "Spot Light",
+ value: "SpotLight",
+ },
+ ],
+ },
+ lightProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Intensity",
+ value: "intensity",
+ },
+ {
+ text: "Cast Shadow?",
+ value: "castShadow",
+ },
+ {
+ text: "Ground Color (HemisphereLight)",
+ value: "groundColor",
+ },
+ {
+ text: "Map (SpotLight)",
+ value: "map",
+ },
+ {
+ text: "Distance (SpotLight)",
+ value: "distance",
+ },
+ {
+ text: "Decay (SpotLight)",
+ value: "decay",
+ },
+ {
+ text: "Penumbra (SpotLight)",
+ value: "penumbra",
+ },
+ {
+ text: "Angle/Size (SpotLight)",
+ value: "angle",
+ },
+ {
+ text: "Power (SpotLight)",
+ value: "power",
+ },
+ {
+ text: "Target Position (Directional/SpotLight)",
+ value: "target",
+ },
+ ],
+ },
+ },
+ };
+ }
- reader.readAsArrayBuffer(file);
- })
- }
- openConv() {{open("https://gero3.github.io/facetype.js/")}}
+ addLight(args) {
+ const light = new THREE[args.TYPE](0xffffff, 1);
- }
- Scratch.extensions.register(new ThreeObjects())
-
- class ThreeLights {
- getInfo() {
- return {
- id: "threeLights",
- name: "Three Lights",
- color1: "#c7a22aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addLight", blockType: Scratch.BlockType.COMMAND, text: "add light [NAME] type [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "lightTypes"}}},
- {opcode: "setLight", blockType: Scratch.BlockType.COMMAND, text: "set light [NAME][PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lightProperties", defaultValue: "intensity"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1", exemptFromNormalization: true}}},
- ],
- menus: {
- lightTypes: {acceptReporters: false, items: [
- {text: "Ambient Light", value: "AmbientLight"},{text: "Directional Light", value: "DirectionalLight"},{text: "Point Light", value: "PointLight"},{text: "Hemisphere Light", value: "HemisphereLight"},{text: "Spot Light", value: "SpotLight"},
- ]},
- lightProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Intensity", value: "intensity"},{text: "Cast Shadow?", value: "castShadow"},
- {text: "Ground Color (HemisphereLight)", value: "groundColor"},
- {text: "Map (SpotLight)", value: "map"},{text: "Distance (SpotLight)", value: "distance"},{text: "Decay (SpotLight)", value: "decay"},{text: "Penumbra (SpotLight)", value: "penumbra"},{text: "Angle/Size (SpotLight)", value: "angle"},{text: "Power (SpotLight)", value: "power"},
- {text: "Target Position (Directional/SpotLight)", value: "target"},
- ]},
- }
- }}
+ createObject(args.NAME, light, args.GROUP);
+ lights[args.NAME] = light;
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
- addLight(args) {
- const light = new THREE[args.TYPE](0xffffff, 1)
-
- createObject(args.NAME, light, args.GROUP)
- lights[args.NAME] = light
- if (light.type === "AmbientLight" || "HemisphereLight") return
-
- light.castShadow = true
- if (light.type === "PointLight") return
- //Directional & Spot Light
- light.target.position.set(0, 0, 0)
- scene.add(light.target)
-
- light.pos = new THREE.Vector3(0,0,0)
-
- light.shadow.mapSize.width = 4096
- light.shadow.mapSize.height = 2048
-
- if (light.type === "SpotLight") {
- light.decay = 0
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true
- light.needsUpdate = true
- }
+ light.castShadow = true;
+ if (light.type === "PointLight") return;
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0);
+ scene.add(light.target);
- setLight(args) {
- const light = lights[args.NAME]
- if (!args.PROPERTY) return
- if (args.PROPERTY === "target") {
- light.target.position.set(...JSON.parse(args.VALUE)) //vector3
- light.target.updateMatrixWorld();
- }
- else {
- light[args.PROPERTY] = getAsset(args.VALUE)
+ light.pos = new THREE.Vector3(0, 0, 0);
+
+ light.shadow.mapSize.width = 4096;
+ light.shadow.mapSize.height = 2048;
+
+ if (light.type === "SpotLight") {
+ light.decay = 0;
+ light.shadow.camera.near = 500;
+ light.shadow.camera.far = 4000;
+ light.shadow.camera.fov = 30;
+ }
+ light.shadow.needsUpdate = true;
+ light.needsUpdate = true;
}
- light.needsUpdate = true
- if (light.type === "AmbientLight" || "HemisphereLight") return
+ setLight(args) {
+ const light = lights[args.NAME];
+ if (!args.PROPERTY) return;
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)); //vector3
+ light.target.updateMatrixWorld();
+ } else {
+ light[args.PROPERTY] = getAsset(args.VALUE);
+ }
+ light.needsUpdate = true;
+
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
}
+ Scratch.extensions.register(new ThreeLights());
- }
- Scratch.extensions.register(new ThreeLights())
-
- class ThreeUtilities {
- getInfo() {
- return {
- id: "threeUtility",
- name: "Three Utilities",
- color1: "#3875c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newVector2", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}}},
- {opcode: "newVector3", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y] [Z]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}, Z: {type: Scratch.ArgumentType.NUMBER}}},
+ class ThreeUtilities {
+ getInfo() {
+ return {
+ id: "threeUtility",
+ name: "Three Utilities",
+ color1: "#3875c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newVector2",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ {
+ opcode: "newVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y] [Z]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Z: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
"---",
- {opcode: "operateV3", blockType: Scratch.BlockType.REPORTER, text: "do [V3] [O] [V32]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, O: {type: Scratch.ArgumentType.STRING, menu: "operators"}, V32: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "moveVector3", blockType: Scratch.BlockType.REPORTER, text: "move [S] steps in vector [V3] in direction [D3]", arguments: {S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "directionTo", blockType: Scratch.BlockType.REPORTER, text: "direction from [V3] to [T3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, T3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ {
+ opcode: "operateV3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "do [V3] [O] [V32]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ O: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "operators",
+ },
+ V32: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "moveVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "move [S] steps in vector [V3] in direction [D3]",
+ arguments: {
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "directionTo",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "direction from [V3] to [T3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ T3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
"---",
- {opcode: "newColor", blockType: Scratch.BlockType.REPORTER, text: "New Color [HEX]", arguments: {HEX: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff"}}},
- {opcode: "newFog", blockType: Scratch.BlockType.REPORTER, text: "New Fog [COLOR] [NEAR] [FAR]", arguments: {COLOR: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff", exemptFromNormalization: true}, NEAR: {type: Scratch.ArgumentType.NUMBER}, FAR: {type: Scratch.ArgumentType.NUMBER, defaultValue: 10}}},
- {opcode: "newTexture", blockType: Scratch.BlockType.REPORTER, text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newCubeTexture", blockType: Scratch.BlockType.REPORTER, text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]", arguments: {"COSTUMEX0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEX1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ1": {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newEquirectangularTexture", blockType: Scratch.BlockType.REPORTER, text: "New Equirectangular Texture [COSTUME] [MODE]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"}}},
+ {
+ opcode: "newColor",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Color [HEX]",
+ arguments: {
+ HEX: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ },
+ },
+ },
+ {
+ opcode: "newFog",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Fog [COLOR] [NEAR] [FAR]",
+ arguments: {
+ COLOR: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ exemptFromNormalization: true,
+ },
+ NEAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ FAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 10,
+ },
+ },
+ },
+ {
+ opcode: "newTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newCubeTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUMEX0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEX1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newEquirectangularTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Equirectangular Texture [COSTUME] [MODE]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ },
+ },
"---",
- {opcode: "curve", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]", arguments: {TYPE: {type: Scratch.ArgumentType.STRING, menu: "curveTypes"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]"}, CLOSED: {type: Scratch.ArgumentType.STRING, defaultValue: "true"}}},
+ {
+ opcode: "curve",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
+ arguments: {
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "curveTypes",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
+ },
+ CLOSED: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ },
+ },
+ },
"---",
- {opcode:"mouseDown",extensions: ["colours_sensing"], blockType: Scratch.BlockType.BOOLEAN, text: "mouse [BUTTON] [action]?", arguments: {BUTTON: {type: Scratch.ArgumentType.STRING, menu: "mouseButtons"},action: {type: Scratch.ArgumentType.STRING, menu: "mouseAction"}}},
- {opcode: "mousePos",extensions: ["colours_sensing"], blockType: Scratch.BlockType.REPORTER, text: "mouse position", arguments: {}},
+ {
+ opcode: "mouseDown",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "mouse [BUTTON] [action]?",
+ arguments: {
+ BUTTON: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseButtons",
+ },
+ action: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseAction",
+ },
+ },
+ },
+ {
+ opcode: "mousePos",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "mouse position",
+ arguments: {},
+ },
"---",
- {opcode: "getItem",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "get item [ITEM] of [ARRAY]", arguments: {ITEM: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}, ARRAY: {type: Scratch.ArgumentType.STRING, defaultValue: `["myObject", "myLight"]`}}},
- {blockType: Scratch.BlockType.LABEL, text: "↳ Raycasting"},
- {opcode: "raycast", blockType: Scratch.BlockType.COMMAND, text: "Raycast from [V3] in direction [D3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,1]"}}},
- {opcode: "getRaycast", blockType: Scratch.BlockType.REPORTER, text: "get raycast [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "raycastProperties"}}},
-
- ],
- menus: {
- materialProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Map (texture)", value: "map"},{text: "Alpha Map (texture)", value: "alphaMap"},{text: "Alpha Test (0-1)", value: "alphaTest"},{text: "Side (front/back/double)", value: "side"},{text: "Bump Map (texture)", value: "bumpMap"},{text: "Bump Scale", value: "bumpScale"},
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- raycastProperties: {acceptReporters: false, items: [
- {text: "Intersected Object Names", value: "name"},{text: "Number of Objects", value: "number"},{text: "Intersected Objects distances", value: "distance"},
- ]},
- mouseButtons: {acceptReporters: false, items: ["left","middle","right"]},
- mouseAction: {acceptReporters: false, items: ["Down","Clicked"]},
- curveTypes: {acceptReporters: false, items: ["CatmullRomCurve3"]},
- operators: {acceptReporters: false, items: [
- "+","-","*","/","=","max","min","dot","cross","distance to","angle to","apply euler",
- ]}
+ {
+ opcode: "getItem",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get item [ITEM] of [ARRAY]",
+ arguments: {
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ ARRAY: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: `["myObject", "myLight"]`,
+ },
+ },
+ },
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Raycasting",
+ },
+ {
+ opcode: "raycast",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Raycast from [V3] in direction [D3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,1]",
+ },
+ },
+ },
+ {
+ opcode: "getRaycast",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get raycast [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "raycastProperties",
+ },
+ },
+ },
+ ],
+ menus: {
+ materialProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map (texture)",
+ value: "map",
+ },
+ {
+ text: "Alpha Map (texture)",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test (0-1)",
+ value: "alphaTest",
+ },
+ {
+ text: "Side (front/back/double)",
+ value: "side",
+ },
+ {
+ text: "Bump Map (texture)",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ raycastProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Intersected Object Names",
+ value: "name",
+ },
+ {
+ text: "Number of Objects",
+ value: "number",
+ },
+ {
+ text: "Intersected Objects distances",
+ value: "distance",
+ },
+ ],
+ },
+ mouseButtons: {
+ acceptReporters: false,
+ items: ["left", "middle", "right"],
+ },
+ mouseAction: {
+ acceptReporters: false,
+ items: ["Down", "Clicked"],
+ },
+ curveTypes: {
+ acceptReporters: false,
+ items: ["CatmullRomCurve3"],
+ },
+ operators: {
+ acceptReporters: false,
+ items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
+ },
+ },
+ };
+ }
+ mouseDown(args) {
+ if (args.action === "Down") return isMouseDown[args.BUTTON];
+ if (args.action === "Clicked") {
+ if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
+ else prevMouse[args.BUTTON] = true;
+ return true;
}
- }}
- mouseDown(args) {
- if (args.action === "Down") return isMouseDown[args.BUTTON]
- if (args.action === "Clicked") {
- if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false
- else prevMouse[args.BUTTON] = true; return true
}
- }
- mousePos(event) {
- return JSON.stringify(mouseNDC)
- }
- newVector3(args) {
- return JSON.stringify([args.X, args.Y, args.Z])
- }
- operateV3(args){
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const v32 = new THREE.Vector3(...JSON.parse(args.V32))
-
- let r
- if (args.O == "+") r = v3.add(v32)
- else if (args.O == "-") r = v3.sub(v32)
- else if (args.O == "*") r = v3.multiply(v32)
- else if (args.O == "/") r = v3.divide(v32)
- else if (args.O == "=") r = v3.equals(v32)
- else if (args.O == "max") r = v3.max(v32)
- else if (args.O == "min") r = v3.min(v32)
- else if (args.O == "dot") r = v3.dot(v32)
- else if (args.O == "cross") r = v3.cross(v32)
- else if (args.O == "distance to") r = v3.distanceTo(v32)
- else if (args.O == "angle to") r = v3.angleTo(v32)
- else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder))
-
- if (typeof(r) == "object") return JSON.stringify([r.x, r.y, r.z])
- else return JSON.stringify(r)
- }
-
- newVector2(args) {
- return JSON.stringify([args.X, args.Y])
- }
+ mousePos(event) {
+ return JSON.stringify(mouseNDC);
+ }
+ newVector3(args) {
+ return JSON.stringify([args.X, args.Y, args.Z]);
+ }
+ operateV3(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const v32 = new THREE.Vector3(...JSON.parse(args.V32));
+
+ let r;
+ if (args.O == "+") r = v3.add(v32);
+ else if (args.O == "-") r = v3.sub(v32);
+ else if (args.O == "*") r = v3.multiply(v32);
+ else if (args.O == "/") r = v3.divide(v32);
+ else if (args.O == "=") r = v3.equals(v32);
+ else if (args.O == "max") r = v3.max(v32);
+ else if (args.O == "min") r = v3.min(v32);
+ else if (args.O == "dot") r = v3.dot(v32);
+ else if (args.O == "cross") r = v3.cross(v32);
+ else if (args.O == "distance to") r = v3.distanceTo(v32);
+ else if (args.O == "angle to") r = v3.angleTo(v32);
+ else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
+
+ if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
+ else return JSON.stringify(r);
+ }
- moveVector3(args) {
- const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
- const steps = Number(args.S);
+ newVector2(args) {
+ return JSON.stringify([args.X, args.Y]);
+ }
- const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
+ moveVector3(args) {
+ const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
+ const steps = Number(args.S);
- const yaw = THREE.MathUtils.degToRad(yawInputDeg);
- const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
- const roll = THREE.MathUtils.degToRad(rollInputDeg);
+ const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
- const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
+ const yaw = THREE.MathUtils.degToRad(yawInputDeg);
+ const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
+ const roll = THREE.MathUtils.degToRad(rollInputDeg);
- const forwardVector = new THREE.Vector3(0, 0, -1);
- const direction = forwardVector.applyEuler(euler).normalize();
+ const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
- const newPos = currentPos.add(direction.multiplyScalar(steps));
- return JSON.stringify([newPos.x, newPos.y, newPos.z]);
- }
+ const forwardVector = new THREE.Vector3(0, 0, -1);
+ const direction = forwardVector.applyEuler(euler).normalize();
- directionTo(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const toV3 = new THREE.Vector3(...JSON.parse(args.T3))
+ const newPos = currentPos.add(direction.multiplyScalar(steps));
+ return JSON.stringify([newPos.x, newPos.y, newPos.z]);
+ }
- const direction = toV3.clone().sub(v3).normalize();
- // Pitch (X)
- const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x*direction.x + direction.z*direction.z));
- // Yaw (Y)
- const yaw = Math.atan2(direction.x, direction.z);
+ directionTo(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
- // Roll always 0
- return JSON.stringify([180+THREE.MathUtils.radToDeg(pitch),THREE.MathUtils.radToDeg(yaw),0])
- }
+ const direction = toV3.clone().sub(v3).normalize();
+ // Pitch (X)
+ const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
+ // Yaw (Y)
+ const yaw = Math.atan2(direction.x, direction.z);
- newColor(args) {
- const color = new THREE.Color(args.HEX)
- const uuid = crypto.randomUUID()
- assets.colors[uuid] = color
- return `colors/${uuid}`
- }
- newFog(args) {
- const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR)
- const uuid = crypto.randomUUID()
- assets.fogs[uuid] = fog
- return `fogs/${uuid}`
- }
- async newTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newCubeTexture(args) {
- const uris = [encodeCostume(args.COSTUMEX0),encodeCostume(args.COSTUMEX1), encodeCostume(args.COSTUMEY0),encodeCostume(args.COSTUMEY1), encodeCostume(args.COSTUMEZ0),encodeCostume(args.COSTUMEZ1)]
- const normalized = await Promise.all(uris.map(uri => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
- texture.mapping = THREE.EquirectangularReflectionMapping
-
- setTexutre(texture, args.MODE)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
+ // Roll always 0
+ return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
+ }
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g)
- if (!matches) return []
-
- return matches.map(str => {
- const nums = str
- .replace(/[\[\]\s]/g, '')
- .split(',')
- .map(Number)
- return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0)
- })
- }
- const points = parsePoints(args.POINTS)
- const curve = new THREE[args.TYPE](points)
- curve.closed = JSON.parse(args.CLOSED)
-
- const uuid = crypto.randomUUID()
- assets.curves[uuid] = curve
- return `curves/${uuid}`
- }
+ newColor(args) {
+ const color = new THREE.Color(args.HEX);
+ const uuid = crypto.randomUUID();
+ assets.colors[uuid] = color;
+ return `colors/${uuid}`;
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
+ const uuid = crypto.randomUUID();
+ assets.fogs[uuid] = fog;
+ return `fogs/${uuid}`;
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newCubeTexture(args) {
+ const uris = [
+ encodeCostume(args.COSTUMEX0),
+ encodeCostume(args.COSTUMEX1),
+ encodeCostume(args.COSTUMEY0),
+ encodeCostume(args.COSTUMEY1),
+ encodeCostume(args.COSTUMEZ0),
+ encodeCostume(args.COSTUMEZ1),
+ ];
+ const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
+
+ texture.name = "CubeTexture" + args.COSTUMEX0;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+ texture.mapping = THREE.EquirectangularReflectionMapping;
+
+ setTexutre(texture, args.MODE);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
- getItem(args) {
- const items = JSON.parse(args.ARRAY)
- const item = items[args.ITEM - 1]
- if (!item) return "0"
- return item
- }
+ curve(args) {
+ function parsePoints(input) {
+ // Match all [x,y,z] groups
+ const matches = input.match(/\[([^\]]+)\]/g);
+ if (!matches) return [];
+
+ return matches.map((str) => {
+ const nums = str
+ .replace(/[\[\]\s]/g, "")
+ .split(",")
+ .map(Number);
+ return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
+ });
+ }
+ const points = parsePoints(args.POINTS);
+ const curve = new THREE[args.TYPE](points);
+ curve.closed = JSON.parse(args.CLOSED);
+
+ const uuid = crypto.randomUUID();
+ assets.curves[uuid] = curve;
+ return `curves/${uuid}`;
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY);
+ const item = items[args.ITEM - 1];
+ if (!item) return "0";
+ return item;
+ }
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3))
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3));
// rotation is in degrees => convert to radians first
- const rot = JSON.parse(args.D3).map(v => v * Math.PI / 180)
+ const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
- const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder)
- const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize()
+ const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
+ const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
- const raycaster = new THREE.Raycaster()
- //const camera = getObject(args.CAMERA)
- raycaster.set( origin, direction );
+ const raycaster = new THREE.Raycaster();
+ //const camera = getObject(args.CAMERA)
+ raycaster.set(origin, direction);
- const intersects = raycaster.intersectObjects( scene.children, true )
+ const intersects = raycaster.intersectObjects(scene.children, true);
- raycastResult = intersects
- }
- getRaycast(args) {
- if (args.PROPERTY === "number") return raycastResult.length
- if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map(i => i.distance))
- return JSON.stringify(raycastResult.map(i => i.object[args.PROPERTY]))
+ raycastResult = intersects;
+ }
+ getRaycast(args) {
+ if (args.PROPERTY === "number") return raycastResult.length;
+ if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
+ return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
+ }
}
+ Scratch.extensions.register(new ThreeUtilities());
- }
- Scratch.extensions.register(new ThreeUtilities())
-
- class ThreeGLB {
- getInfo() {
- return {
- id: "threeGLB",
- name: "Three GLB Loader",
- color1: "#c53838ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Load GLB File", func: "loadModelFile"},
- {opcode: "addModel", blockType: Scratch.BlockType.COMMAND, text: "add [ITEM] as [NAME] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},ITEM: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}}},
- {opcode: "getModel", blockType: Scratch.BlockType.REPORTER, text: "get object [PROPERTY] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "modelProperties"}}},
- {opcode: "playAnimation", blockType: Scratch.BlockType.COMMAND, text: "play animation [ANAME] of [NAME], [TIMES] times", arguments: {TIMES: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "pauseAnimation", blockType: Scratch.BlockType.COMMAND, text: "set [TOGGLE] animation [ANAME] of [NAME]", arguments: {TOGGLE: {type: Scratch.ArgumentType.NUMBER, menu: "pauseUn"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "stopAnimation", blockType: Scratch.BlockType.COMMAND, text: "stop animation [ANAME] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
-
- ],
- menus: {
- modelProperties: {acceptReporters: false, items: [
- {text: "Animations", value: "animations"},
- ]},
- pauseUn: {acceptReporters: true, items: [{text: "Pause", value: "true"},{text: "Unpasue", value: "false"},]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
+ class ThreeGLB {
+ getInfo() {
+ return {
+ id: "threeGLB",
+ name: "Three GLB Loader",
+ color1: "#c53838ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load GLB File",
+ func: "loadModelFile",
+ },
+ {
+ opcode: "addModel",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add [ITEM] as [NAME] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ },
+ },
+ {
+ opcode: "getModel",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get object [PROPERTY] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelProperties",
+ },
+ },
+ },
+ {
+ opcode: "playAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "play animation [ANAME] of [NAME], [TIMES] times",
+ arguments: {
+ TIMES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "pauseAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [TOGGLE] animation [ANAME] of [NAME]",
+ arguments: {
+ TOGGLE: {
+ type: Scratch.ArgumentType.NUMBER,
+ menu: "pauseUn",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "stopAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stop animation [ANAME] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ modelProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Animations",
+ value: "animations",
+ }, ],
+ },
+ pauseUn: {
+ acceptReporters: true,
+ items: [{
+ text: "Pause",
+ value: "true",
+ },
+ {
+ text: "Unpasue",
+ value: "false",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
// @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- }
- }}
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
- async loadModelFile() {
+ async loadModelFile() {
+ openFileExplorer(".glb").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
- openFileExplorer(".glb").then(files => {
- const file = files[0];
- const reader = new FileReader();
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- { // From lily's assets
+ {
+ // From lily's assets
// Thank you PenguinMod for providing this code.
- {
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
+ {
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ //const res = await Scratch.fetch(args.URL);
+ //const buffer = await res.arrayBuffer();
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Model loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading model.");
+ }
+ }
+ // End of PenguinMod
+ }
+ };
- //const res = await Scratch.fetch(args.URL);
- //const buffer = await res.arrayBuffer();
- const buffer = arrayBuffer
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ async addModel(args) {
+ const group = await getModel(args.ITEM, args.NAME);
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
+ createObject(args.NAME, group, args.GROUP);
+ }
+ getModel(args) {
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString();
+ }
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Model loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading model.");
- }
- }
- // End of PenguinMod
+ playAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) {
+ console.log("no model!");
+ return;
}
- };
- reader.readAsArrayBuffer(file);
- })
-
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME)
-
- createObject(args.NAME, group, args.GROUP)
- }
- getModel(args){
- if (!models[args.NAME]) return;
- return Object.keys(models[args.NAME].actions).toString()
- }
+ const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
+ if (!action) {
+ console.log("no action!");
+ return;
+ }
- playAnimation(args) {
- const model = models[args.NAME]
- if (!model) {console.log("no model!"); return}
+ args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
- const action = model.actions[args.ANAME] //clones of models dont have a stored actions!
- if (!action) {
- console.log("no action!")
- return
+ action.reset().play();
}
+ stopAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
- args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity)
-
- action.reset()
- .play()
- }
- stopAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.stop();
- }
- pauseAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
+ const action = model.actions[args.ANAME];
+ if (action) action.stop();
+ }
+ pauseAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
- const action = model.actions[args.ANAME];
- if (action) action.paused = args.TOGGLE
+ const action = model.actions[args.ANAME];
+ if (action) action.paused = args.TOGGLE;
+ }
}
+ Scratch.extensions.register(new ThreeGLB());
- }
- Scratch.extensions.register(new ThreeGLB())
-
- class ThreeAddons {
- getInfo() {
- return {
- id: "threeAddons",
- name: "Three Addons",
- color1: "#c538a2ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.LABEL, text: "Orbit Control"},
- {opcode: "OrbitControl", blockType: Scratch.BlockType.COMMAND, text: "set addon Orbit Control [STATE]", arguments: {STATE: {type: Scratch.ArgumentType.STRING, menu: "onoff"},}},
-
- {blockType: Scratch.BlockType.LABEL, text: "Post Processing"},
- {opcode: "resetComposer", blockType: Scratch.BlockType.COMMAND, text: "reset composer"},
- {opcode: "bloom", blockType: Scratch.BlockType.COMMAND, text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},I: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, T:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "godRays", blockType: Scratch.BlockType.COMMAND, text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}, DEC:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.95}, DENS:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},EXP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1},WEI:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.4},RES:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},SAMP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 64},}},
- {opcode: "dots", blockType: Scratch.BlockType.COMMAND, text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, A: {type: Scratch.ArgumentType.ANGLE, defaultValue: 0},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "depth", blockType: Scratch.BlockType.COMMAND, text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]", arguments: {FD: {type: Scratch.ArgumentType.NUMBER, defaultValue: (3)},FL: {type: Scratch.ArgumentType.NUMBER, defaultValue: (0.001)},BS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 4},H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 240},OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}}},
+ class ThreeAddons {
+ getInfo() {
+ return {
+ id: "threeAddons",
+ name: "Three Addons",
+ color1: "#c538a2ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.LABEL,
+ text: "Orbit Control",
+ },
+ {
+ opcode: "OrbitControl",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set addon Orbit Control [STATE]",
+ arguments: {
+ STATE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "onoff",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "Post Processing",
+ },
+ {
+ opcode: "resetComposer",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset composer",
+ },
+ {
+ opcode: "bloom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ I: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ T: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "godRays",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ DEC: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.95,
+ },
+ DENS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ EXP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ WEI: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.4,
+ },
+ RES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ SAMP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 64,
+ },
+ },
+ },
+ {
+ opcode: "dots",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ A: {
+ type: Scratch.ArgumentType.ANGLE,
+ defaultValue: 0,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "depth",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ FD: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 3,
+ },
+ FL: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.001,
+ },
+ BS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 4,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 240,
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ },
+ },
"---",
- {opcode: "custom", blockType: Scratch.BlockType.COMMAND, text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myShader"}, FRA: {type: Scratch.ArgumentType.STRING}, VER: {type: Scratch.ArgumentType.STRING}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}, OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
+ {
+ opcode: "custom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myShader",
+ },
+ FRA: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ VER: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
],
- menus: {
- onoff: {acceptReporters: true, items: [{text: "enabled", value: "1"},{text: "disabled", value: "0"},]},
- blendModes: {acceptReporters: false, items: [
- "SKIP","SET","ADD","ALPHA","AVERAGE","COLOR","COLOR_BURN","COLOR_DODGE",
- "DARKEN","DIFFERENCE","DIVIDE","DST","EXCLUSION","HARD_LIGHT","HARD_MIX",
- "HUE","INVERT","INVERT_RGB","LIGHTEN","LINEAR_BURN","LINEAR_DODGE",
- "LINEAR_LIGHT","LUMINOSITY","MULTIPLY","NEGATION","NORMAL","OVERLAY",
- "PIN_LIGHT","REFLECT","SCREEN","SRC","SATURATION","SOFT_LIGHT","SUBTRACT",
- "VIVID_LIGHT"
- ]},
- }
- }}
+ menus: {
+ onoff: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "1",
+ },
+ {
+ text: "disabled",
+ value: "0",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [
+ "SKIP",
+ "SET",
+ "ADD",
+ "ALPHA",
+ "AVERAGE",
+ "COLOR",
+ "COLOR_BURN",
+ "COLOR_DODGE",
+ "DARKEN",
+ "DIFFERENCE",
+ "DIVIDE",
+ "DST",
+ "EXCLUSION",
+ "HARD_LIGHT",
+ "HARD_MIX",
+ "HUE",
+ "INVERT",
+ "INVERT_RGB",
+ "LIGHTEN",
+ "LINEAR_BURN",
+ "LINEAR_DODGE",
+ "LINEAR_LIGHT",
+ "LUMINOSITY",
+ "MULTIPLY",
+ "NEGATION",
+ "NORMAL",
+ "OVERLAY",
+ "PIN_LIGHT",
+ "REFLECT",
+ "SCREEN",
+ "SRC",
+ "SATURATION",
+ "SOFT_LIGHT",
+ "SUBTRACT",
+ "VIVID_LIGHT",
+ ],
+ },
+ },
+ };
+ }
OrbitControl(args) {
- if (controls) controls.dispose()
+ if (controls) controls.dispose();
- console.log("creating...", OrbitControls)
+ console.log("creating...", OrbitControls);
controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
- controls.enableDamping = true
-
- controls.enabled = !!args.STATE
- console.log(controls)
- }
+ controls.enableDamping = true;
- resetComposer() {
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
+ controls.enabled = !!args.STATE;
+ console.log(controls);
+ }
- bloom(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const bloomEffect = new BloomEffect({
- intensity: args.I,
- luminanceThreshold: args.T, // ← correct key
- luminanceSmoothing: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- bloomEffect.blendMode.opacity.value = args.OP
+ resetComposer() {
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
- const pass = new EffectPass(camera, bloomEffect)
+ bloom(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ bloomEffect.blendMode.opacity.value = args.OP;
- composer.addPass(pass)
- }
+ const pass = new EffectPass(camera, bloomEffect);
- godRays(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- let object = getObject(args.NAME)
- const sun = object
-
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- })
- godRays.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, godRays)
- composer.addPass(pass)
- }
+ composer.addPass(pass);
+ }
- dots(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dot = new DotScreenEffect({
- angle: args.A,
- scale: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- dot.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, dot)
- composer.addPass(pass)
- }
+ godRays(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ let object = getObject(args.NAME);
+ const sun = object;
+
+ const godRays = new GodRaysEffect(camera, sun, {
+ resolutionScale: args.RES,
+ density: args.DENS, // ray density
+ decay: args.DEC, // fade out
+ weight: args.WEI, // brightness of rays
+ exposure: args.EXP,
+ samples: args.SAMP,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ godRays.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, godRays);
+ composer.addPass(pass);
+ }
- depth(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- })
- dofEffect.blendMode.opacity.value = args.OP
-
- const dofPass = new EffectPass(camera, dofEffect)
- composer.addPass(dofPass)
- }
+ dots(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dot.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, dot);
+ composer.addPass(pass);
+ }
- async custom(args) {
- function cleanGLSL(glslCode) {
- //delete multilines comments
- let cleanedCode = glslCode.replace(/\/\*[\s\S]*?\*\//g, ' ')
- .replace(/ /g, '\n')
- .replace(/\/\/.*$/gm, ' ')
- .replace(/; /g, ';\n')
-
- return cleanedCode;
+ depth(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dofEffect.blendMode.opacity.value = args.OP;
+
+ const dofPass = new EffectPass(camera, dofEffect);
+ composer.addPass(dofPass);
}
- let fs = cleanGLSL(`
+ async custom(args) {
+ function cleanGLSL(glslCode) {
+ //delete multilines comments
+ let cleanedCode = glslCode
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
+ .replace(/ /g, "\n")
+ .replace(/\/\/.*$/gm, " ")
+ .replace(/; /g, ";\n");
+
+ return cleanedCode;
+ }
+
+ let fs = cleanGLSL(`
${args.FRA}
- `)
- if (!args.FRA.trim()) {fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`}
- const vs = cleanGLSL(`
+ `);
+ if (!args.FRA.trim()) {
+ fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
+ }
+ const vs = cleanGLSL(`
${args.VER}
- `)
- console.log(fs)
- console.log(vs)
+ `);
+ console.log(fs);
+ console.log(vs);
- const effect = new Effect(
- "Custom",
- fs,
- {
+ const effect = new Effect("Custom", fs, {
blendFunction: BlendFunction[args.BLEND],
vertexShader: vs,
- uniforms: new Map([ //uniforms usually in shaders... open to more!
- ['time', new THREE.Uniform(0.0)],
- ['resolution', new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height))]
+ uniforms: new Map([
+ //uniforms usually in shaders... open to more!
+ ["time", new THREE.Uniform(0.0)],
+ [
+ "resolution",
+ new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
+ ],
]),
- defines: new Map([['USE_TIME', '1'], ['USE_VERTEX_TRANSFORM', '']]),
- }
- );
+ defines: new Map([
+ ["USE_TIME", "1"],
+ ["USE_VERTEX_TRANSFORM", ""],
+ ]),
+ });
- effect.blendMode.opacity.value = args.OP
+ effect.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, effect);
- composer.addPass(pass);
+ const pass = new EffectPass(camera, effect);
+ composer.addPass(pass);
- customEffects.push(effect);
+ customEffects.push(effect);
+ }
}
+ Scratch.extensions.register(new ThreeAddons());
- }
- Scratch.extensions.register(new ThreeAddons())
-
- class RapierPhysics {
+ class RapierPhysics {
getInfo() {
return {
id: "rapierPhysics",
@@ -1956,119 +3917,679 @@ Promise.resolve(load()).then(() => {
color1: "#222222",
color2: "#203024ff",
color3: "#78f07eff",
- blocks: [
- {opcode: "createWorld", blockType: Scratch.BlockType.COMMAND, text: "create world | gravity:[G]", arguments: {G: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,-9.81,0]"}}},
- {opcode: "getWorld", blockType: Scratch.BlockType.REPORTER, text: "get world [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "wProp"}}},
+ blocks: [{
+ opcode: "createWorld",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create world | gravity:[G]",
+ arguments: {
+ G: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,-9.81,0]",
+ },
+ },
+ },
+ {
+ opcode: "getWorld",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get world [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "wProp",
+ },
+ },
+ },
"---",
- {opcode: "objectPhysics", blockType: Scratch.BlockType.COMMAND, text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]", arguments: {state2: {type: Scratch.ArgumentType.STRING, menu: "state2"},state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"}, type: {type: Scratch.ArgumentType.STRING, menu: "objectTypes", defaultValue: "dynamic"}, collider: {type: Scratch.ArgumentType.STRING, menu: "colliderTypes", defaultValue: "cuboid"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},mass: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},density: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},friction: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0.5"}}},
+ {
+ opcode: "objectPhysics",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
+ arguments: {
+ state2: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state2",
+ },
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ type: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ defaultValue: "dynamic",
+ },
+ collider: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderTypes",
+ defaultValue: "cuboid",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ mass: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ density: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ friction: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0.5",
+ },
+ },
+ },
"---",
- {blockType: Scratch.BlockType.LABEL, text: "- RigidBody"},
- {opcode: "setRB", blockType: Scratch.BlockType.COMMAND, text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodySets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getRB", blockType: Scratch.BlockType.REPORTER, text: "get rigidbody [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodyProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- RigidBody",
+ },
+ {
+ opcode: "setRB",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodySets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getRB",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get rigidbody [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodyProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "lockObjectAxis", blockType: Scratch.BlockType.COMMAND, text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]", arguments: {OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lockAxes"}, X: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Y: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Z: {type: Scratch.ArgumentType.STRING, menu: "tf"}}},
+ {
+ opcode: "lockObjectAxis",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
+ arguments: {
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lockAxes",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Y: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Z: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ },
+ },
"---",
- {opcode: "addForce", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,10,0]"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "forces", defaultValue: "addForce"},SPACE: {type: Scratch.ArgumentType.STRING, menu: "spaces", defaultValue: "world"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "resetForces", blockType: Scratch.BlockType.COMMAND, text: "reset [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "resetF", defaultValue: "resetForces"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ opcode: "addForce",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,10,0]",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "forces",
+ defaultValue: "addForce",
+ },
+ SPACE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "spaces",
+ defaultValue: "world",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "resetForces",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "resetF",
+ defaultValue: "resetForces",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "enableCCD", blockType: Scratch.BlockType.COMMAND, text: "enable Continuous Collision Detection for [OBJECT] [state]", arguments: {state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "oPropS", defaultValue: "physics"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ opcode: "enableCCD",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable Continuous Collision Detection for [OBJECT] [state]",
+ arguments: {
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "oPropS",
+ defaultValue: "physics",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "fixedJoint", blockType: Scratch.BlockType.COMMAND, text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},RA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},RB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},}},
- {opcode: "sphericalJoint", blockType: Scratch.BlockType.COMMAND, text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},}},
- {opcode: "revoluteJoint", blockType: Scratch.BlockType.COMMAND, text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},X: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"},}},
+ {
+ opcode: "fixedJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ RA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ RB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "sphericalJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ },
+ },
+ {
+ opcode: "revoluteJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
"---",
- {blockType: Scratch.BlockType.LABEL, text: "- Collider"},
- {opcode: "setC", blockType: Scratch.BlockType.COMMAND, text: "set collider [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderSets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getC", blockType: Scratch.BlockType.REPORTER, text: "get collider [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- Collider",
+ },
+ {
+ opcode: "setC",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderSets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getC",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get collider [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
"---",
- {opcode: "sensorSingle", blockType: Scratch.BlockType.BOOLEAN, text: "is sensor [SENSOR] touching [OBJECT]?", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "sensorAll", blockType: Scratch.BlockType.REPORTER, text: "objects touching sensor [SENSOR]", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}}}
+ {
+ opcode: "sensorSingle",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is sensor [SENSOR] touching [OBJECT]?",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "sensorAll",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "objects touching sensor [SENSOR]",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ },
+ },
],
menus: {
- wProp: {acceptReporters: false, items: [
- {text: "Gravity", value: "gravity"}, {text: "log to console", value: "log"}
- ]},
- tf: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true", value: "true"}]},
- lockAxes: {acceptReporters: false, items: [
- {text: "Translation", value: "setEnabledTranslations"}, {text: "Rotation", value: "setEnabledRotations"}
- ]},
- rigidBodyProperties: {acceptReporters: false, items: [
- {text: "Type", value: "bodyType"},
- {text: "Linear Velocity", value: "linvel"},
- {text: "Angular Velocity", value: "angvel"},
- {text: "Translation (position)", value: "translation"},
- {text: "Rotation (quaternion)", value: "rotation"},
- {text: "Mass", value: "mass"},
- //{text: "Center of Mass", value: "centerOfMass"},
- {text: "Linear Damping", value: "linearDamping"},
- {text: "Angular Damping", value: "angularDamping"},
- {text: "Is Sleeping?", value: "isSleeping"},
- //{text: "Can Sleep?", value: "isCanSleep"},
- {text: "Gravity Scale", value: "gravityScale"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"},
- //{text: "Sleeping", value: "sleeping"}
- ]},
- rigidBodySets: {acceptReporters: false, items: [
- //{text: "Linear Velocity", value: "setLinvel"},
- //{text: "Angular Velocity", value: "setAngvel"},
- //{text: "Mass", value: "setMass"},
- {text: "Gravity Scale", value: "setGravityScale"},
- //{text: "Can Sleep?", value: "setCanSleep"},
- //{text: "Sleeping", value: "sleeping"},
- {text: "Linear Damping", value: "setLinearDamping"},
- {text: "Angular Damping", value: "setAngularDamping"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"}
- ]},
- colliderProperties: {acceptReporters: false, items: [
- //{text: "Collider Type", value: "type"},
- {text: "Is Sensor?", value: "isSensor"},
- {text: "Friction", value: "friction"},
- {text: "Restitution", value: "restitution"},
- {text: "Density", value: "density"},
- {text: "Mass", value: "mass"},
- {text: "Position", value: "translation"},
- {text: "Rotation", value: "rotation"},
- //{text: "Area", value: "area"},
- {text: "Volume", value: "volume"},
- {text: "Collision Groups", value: "collisionGroups"},
- //{text: "Collision Mask", value: "collisionMask"},
- //{text: "Is Enabled?", value: "enabled"},
- //{text: "Contact Count", value: "contactCount"},
- //{text: "RigidBody Handle", value: "rigidBody"}
- ]},
- colliderSets: {acceptReporters: false, items: [
- {text: "Friction", value: "setFriction"},
- {text: "Restitution", value: "setRestitution"},
- {text: "Density", value: "setDensity"},
- {text: "Is Sensor?", value: "setSensor"},
- {text: "Collision Groups", value: "setCollisionGroups"},
- //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
- //{text: "Position Offset", value: "setTranslation"},
- //{text: "Rotation Offset", value: "setRotation"}
- ]},
- state: {acceptReporters: true, items: [{text: "on", value: "true"},{text: "off", value: "false"}]},
- state2: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true (must be fixed)", value: "true"}]},
- spaces: {acceptReporters: false, items: [{text: "World", value: "world"},{text: "Local", value: "local"}]},
- objectTypes: {acceptReporters: false, items: [{text: "Dynamic", value: "dynamic"},{text: "Fixed", value: "fixed"},{text: "Kinematic Position Based",value: "kinematicPositionBased"}]},
- colliderTypes: {acceptReporters: false, items: [{text: "Box, Rectangle, cuboid", value: "cuboid"},{text: "Sphere, ball", value: "ball"},{text: "Custom, complex simple shapes, convexHull", value: "convexHull"},{text:"Precision, TriMesh",value:"trimesh"}]},
- forces: {acceptReporters: false, items: [{text: "Force", value: "addForce"},{text: "Torque (rotation)", value: "addTorque"},{text: "Apply Impulse", value: "applyImpulse"},{text: "Apply Torque Impulse (rotation)", value: "applyTorqueImpulse"},{text: "Linear Velocity", value: "setLinvel"},{text: "Angular Velocity", value: "setAngvel"},]},
- resetF: {acceptReporters: false, items: [{text:"Forces", value: "resetForces"},{text:"Torques", value: "resetTorques"},]}
- }
- }
+ wProp: {
+ acceptReporters: false,
+ items: [{
+ text: "Gravity",
+ value: "gravity",
+ },
+ {
+ text: "log to console",
+ value: "log",
+ },
+ ],
+ },
+ tf: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true",
+ value: "true",
+ },
+ ],
+ },
+ lockAxes: {
+ acceptReporters: false,
+ items: [{
+ text: "Translation",
+ value: "setEnabledTranslations",
+ },
+ {
+ text: "Rotation",
+ value: "setEnabledRotations",
+ },
+ ],
+ },
+ rigidBodyProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Type",
+ value: "bodyType",
+ },
+ {
+ text: "Linear Velocity",
+ value: "linvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "angvel",
+ },
+ {
+ text: "Translation (position)",
+ value: "translation",
+ },
+ {
+ text: "Rotation (quaternion)",
+ value: "rotation",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ //{text: "Center of Mass", value: "centerOfMass"},
+ {
+ text: "Linear Damping",
+ value: "linearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "angularDamping",
+ },
+ {
+ text: "Is Sleeping?",
+ value: "isSleeping",
+ },
+ //{text: "Can Sleep?", value: "isCanSleep"},
+ {
+ text: "Gravity Scale",
+ value: "gravityScale",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ //{text: "Sleeping", value: "sleeping"}
+ ],
+ },
+ rigidBodySets: {
+ acceptReporters: false,
+ items: [
+ //{text: "Linear Velocity", value: "setLinvel"},
+ //{text: "Angular Velocity", value: "setAngvel"},
+ //{text: "Mass", value: "setMass"},
+ {
+ text: "Gravity Scale",
+ value: "setGravityScale",
+ },
+ //{text: "Can Sleep?", value: "setCanSleep"},
+ //{text: "Sleeping", value: "sleeping"},
+ {
+ text: "Linear Damping",
+ value: "setLinearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "setAngularDamping",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ ],
+ },
+ colliderProperties: {
+ acceptReporters: false,
+ items: [
+ //{text: "Collider Type", value: "type"},
+ {
+ text: "Is Sensor?",
+ value: "isSensor",
+ },
+ {
+ text: "Friction",
+ value: "friction",
+ },
+ {
+ text: "Restitution",
+ value: "restitution",
+ },
+ {
+ text: "Density",
+ value: "density",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ {
+ text: "Position",
+ value: "translation",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ //{text: "Area", value: "area"},
+ {
+ text: "Volume",
+ value: "volume",
+ },
+ {
+ text: "Collision Groups",
+ value: "collisionGroups",
+ },
+ //{text: "Collision Mask", value: "collisionMask"},
+ //{text: "Is Enabled?", value: "enabled"},
+ //{text: "Contact Count", value: "contactCount"},
+ //{text: "RigidBody Handle", value: "rigidBody"}
+ ],
+ },
+ colliderSets: {
+ acceptReporters: false,
+ items: [{
+ text: "Friction",
+ value: "setFriction",
+ },
+ {
+ text: "Restitution",
+ value: "setRestitution",
+ },
+ {
+ text: "Density",
+ value: "setDensity",
+ },
+ {
+ text: "Is Sensor?",
+ value: "setSensor",
+ },
+ {
+ text: "Collision Groups",
+ value: "setCollisionGroups",
+ },
+ //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
+ //{text: "Position Offset", value: "setTranslation"},
+ //{text: "Rotation Offset", value: "setRotation"}
+ ],
+ },
+ state: {
+ acceptReporters: true,
+ items: [{
+ text: "on",
+ value: "true",
+ },
+ {
+ text: "off",
+ value: "false",
+ },
+ ],
+ },
+ state2: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true (must be fixed)",
+ value: "true",
+ },
+ ],
+ },
+ spaces: {
+ acceptReporters: false,
+ items: [{
+ text: "World",
+ value: "world",
+ },
+ {
+ text: "Local",
+ value: "local",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Dynamic",
+ value: "dynamic",
+ },
+ {
+ text: "Fixed",
+ value: "fixed",
+ },
+ {
+ text: "Kinematic Position Based",
+ value: "kinematicPositionBased",
+ },
+ ],
+ },
+ colliderTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box, Rectangle, cuboid",
+ value: "cuboid",
+ },
+ {
+ text: "Sphere, ball",
+ value: "ball",
+ },
+ {
+ text: "Custom, complex simple shapes, convexHull",
+ value: "convexHull",
+ },
+ {
+ text: "Precision, TriMesh",
+ value: "trimesh",
+ },
+ ],
+ },
+ forces: {
+ acceptReporters: false,
+ items: [{
+ text: "Force",
+ value: "addForce",
+ },
+ {
+ text: "Torque (rotation)",
+ value: "addTorque",
+ },
+ {
+ text: "Apply Impulse",
+ value: "applyImpulse",
+ },
+ {
+ text: "Apply Torque Impulse (rotation)",
+ value: "applyTorqueImpulse",
+ },
+ {
+ text: "Linear Velocity",
+ value: "setLinvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "setAngvel",
+ },
+ ],
+ },
+ resetF: {
+ acceptReporters: false,
+ items: [{
+ text: "Forces",
+ value: "resetForces",
+ },
+ {
+ text: "Torques",
+ value: "resetTorques",
+ },
+ ],
+ },
+ },
+ };
}
joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true)
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
}
fixedJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- let RA = JSON.parse(args.RA).map(Number)
- let RB = JSON.parse(args.RB).map(Number)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ let RA = JSON.parse(args.RA).map(Number);
+ let RB = JSON.parse(args.RB).map(Number);
RA = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
@@ -2076,339 +4597,419 @@ Promise.resolve(load()).then(() => {
THREE.MathUtils.degToRad(RA[1]),
THREE.MathUtils.degToRad(RA[2])
)
- )
+ );
RB = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
THREE.MathUtils.degToRad(RB[0]),
THREE.MathUtils.degToRad(RB[1]),
THREE.MathUtils.degToRad(RB[2])
)
- )
-
- const data = RAPIER.JointData.fixed(
- { x: VA[0], y: VA[1], z: VA[2] }, RA,
- { x: VB[0], y: VB[1], z: VB[2] }, RB
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ );
+
+ const data = RAPIER.JointData.fixed({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ },
+ RA, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ },
+ RB
+ );
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
sphericalJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
-
- const data = RAPIER.JointData.spherical(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+
+ const data = RAPIER.JointData.spherical({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
revoluteJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.revolute(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.revolute({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
prismaticJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.prismatic(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.prismatic({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
}
createWorld(args) {
- const v3 = JSON.parse(args.G).map(Number)
- const gravity = { x: v3[0], y: v3[1], z: v3[2]}
- physicsWorld = new RAPIER.World(gravity)
+ const v3 = JSON.parse(args.G).map(Number);
+ const gravity = {
+ x: v3[0],
+ y: v3[1],
+ z: v3[2],
+ };
+ physicsWorld = new RAPIER.World(gravity);
- console.log(physicsWorld)
+ console.log(physicsWorld);
}
getWorld(args) {
- if (args.PROPERTY === "log") {console.log(physicsWorld); return "logged"}
- return JSON.stringify(physicsWorld[args.PROPERTY])
+ if (args.PROPERTY === "log") {
+ console.log(physicsWorld);
+ return "logged";
+ }
+ return JSON.stringify(physicsWorld[args.PROPERTY]);
}
setRB(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.rigidBody[args.PROPERTY](value)
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.rigidBody[args.PROPERTY](value);
}
setC(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.collider[args.PROPERTY](value)
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.collider[args.PROPERTY](value);
}
getRB(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.rigidBody[args.PROPERTY]())
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.rigidBody[args.PROPERTY]());
}
getC(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.collider[args.PROPERTY]())
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.collider[args.PROPERTY]());
}
lockObjectAxis(args) {
- let object = getObject(args.OBJECT)
- const x = !JSON.parse(args.X)
- const y = !JSON.parse(args.Y)
- const z = !JSON.parse(args.Z)
- object.rigidBody[args.PROPERTY](x,y,z,true) //changes is xyz, wake up
+ let object = getObject(args.OBJECT);
+ const x = !JSON.parse(args.X);
+ const y = !JSON.parse(args.Y);
+ const z = !JSON.parse(args.Z);
+ object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
}
objectPhysics(args) {
- let object = getObject(args.OBJECT)
- object.physics = JSON.parse(args.state)
+ let object = getObject(args.OBJECT);
+ object.physics = JSON.parse(args.state);
if (JSON.parse(args.state)) {
//if already exists delete:
if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
}
/*asing a rigidbody and collider to object and add them to physicsWorld*/
- let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
+ let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
.setTranslation(object.position.x, object.position.y, object.position.z)
- .setRotation({w: object.quaternion._w, x: object.quaternion._x, y: object.quaternion._y, z: object.quaternion._z})
-
- let colliderDesc
- switch(args.collider) {
- case "cuboid": colliderDesc = createCuboidCollider(object,); break
- case "ball": colliderDesc = createBallCollider(object); break
- case "convexHull": colliderDesc = createConvexHullCollider(object); break
- case "trimesh": colliderDesc = TriMesh(object); break
- }
- colliderDesc.setSensor(JSON.parse(args.state2)).setMass(args.mass).setDensity(args.density).setFriction(args.friction)
+ .setRotation({
+ w: object.quaternion._w,
+ x: object.quaternion._x,
+ y: object.quaternion._y,
+ z: object.quaternion._z,
+ });
+
+ let colliderDesc;
+ switch (args.collider) {
+ case "cuboid":
+ colliderDesc = createCuboidCollider(object);
+ break;
+ case "ball":
+ colliderDesc = createBallCollider(object);
+ break;
+ case "convexHull":
+ colliderDesc = createConvexHullCollider(object);
+ break;
+ case "trimesh":
+ colliderDesc = TriMesh(object);
+ break;
+ }
+ colliderDesc
+ .setSensor(JSON.parse(args.state2))
+ .setMass(args.mass)
+ .setDensity(args.density)
+ .setFriction(args.friction);
- let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc)
- let collider = physicsWorld.createCollider(colliderDesc, rigidBody)
+ let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
+ let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
- object.rigidBody = rigidBody
- object.collider = collider
+ object.rigidBody = rigidBody;
+ object.collider = collider;
} else {
/*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
}
-
}
enableCCD(args) {
- let object = getObject(args.OBJECT)
+ let object = getObject(args.OBJECT);
if (object.physics) {
- let rigidBody = object.rigidBody
- rigidBody.enableCcd(JSON.parse(args.state))
+ let rigidBody = object.rigidBody;
+ rigidBody.enableCcd(JSON.parse(args.state));
}
- }
+ }
- addForce(args) {
- let object = getObject(args.OBJECT)
- const vector = JSON.parse(args.VALUE).map(Number)
-
- let force = new THREE.Vector3(vector[0],vector[1],vector[2])
+ addForce(args) {
+ let object = getObject(args.OBJECT);
+ const vector = JSON.parse(args.VALUE).map(Number);
+
+ let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
if (args.SPACE === "local") {
force.applyQuaternion(object.quaternion);
}
- object.rigidBody[args.PROPERTY](force,true)
- }
-
- resetForces(args) {
- rigidBody[args.PROPERTY](true)
- }
+ object.rigidBody[args.PROPERTY](force, true);
+ }
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR)
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true);
+ }
- let object = getObject(args.OBJECT)
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR);
- let touching = false
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- if (otherCollider === object.collider) touching = true
- })
+ let object = getObject(args.OBJECT);
- return touching
- }
+ let touching = false;
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ if (otherCollider === object.collider) touching = true;
+ });
- sensorAll(args) {
- const sensor = getObject(args.SENSOR)
+ return touching;
+ }
- const touchedObjects = []
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR);
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- // find owner of collider
- const otherObject = scene.children.find(o => o.collider === otherCollider)
- console.log(otherCollider)
- if (otherObject) touchedObjects.push(otherObject.name)
- })
+ const touchedObjects = [];
- return JSON.stringify(touchedObjects)
- }
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ // find owner of collider
+ const otherObject = scene.children.find((o) => o.collider === otherCollider);
+ console.log(otherCollider);
+ if (otherObject) touchedObjects.push(otherObject.name);
+ });
+ return JSON.stringify(touchedObjects);
+ }
}
- Scratch.extensions.register(new RapierPhysics())
-
- //Thanks to the PointerLock extension of Turbowarp
- const mouse = vm.runtime.ioDevices.mouse;
- let isLocked = false;
- let isPointerLockEnabled = false;
-
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
+ Scratch.extensions.register(new RapierPhysics());
- const postMouseData = (e, isDown) => {
- const { movementX, movementY } = e;
- const { width, height } = rect;
- const x = mouse._clientX + movementX;
- const y = mouse._clientY - movementY;
- mouse._clientX = x;
- mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
- mouse._clientY = y;
- mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
- if (typeof isDown === "boolean") {
- const data = {
- button: e.button,
- isDown,
- };
- originalPostIOData(data);
- }
- };
+ //Thanks to the PointerLock extension of Turbowarp
+ const mouse = vm.runtime.ioDevices.mouse;
+ let isLocked = false;
+ let isPointerLockEnabled = false;
- const mouseDevice = vm.runtime.ioDevices.mouse;
- const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
- mouseDevice.postData = (data) => {
- if (!isPointerLockEnabled) {
- return originalPostIOData(data);
- }
- };
+ let rect = threeRenderer.domElement.getBoundingClientRect();
+ document.addEventListener("resize", () => {
+ rect = threeRenderer.domElement.getBoundingClientRect();
+ });
+
+ const postMouseData = (e, isDown) => {
+ const {
+ movementX,
+ movementY
+ } = e;
+ const {
+ width,
+ height
+ } = rect;
+ const x = mouse._clientX + movementX;
+ const y = mouse._clientY - movementY;
+ mouse._clientX = x;
+ mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
+ mouse._clientY = y;
+ mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
+ if (typeof isDown === "boolean") {
+ const data = {
+ button: e.button,
+ isDown,
+ };
+ originalPostIOData(data);
+ }
+ };
+
+ const mouseDevice = vm.runtime.ioDevices.mouse;
+ const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
+ mouseDevice.postData = (data) => {
+ if (!isPointerLockEnabled) {
+ return originalPostIOData(data);
+ }
+ };
- document.addEventListener(
- "mousedown",
- (e) => {
- // @ts-expect-error
- if (threeRenderer.domElement.contains(e.target)) {
+ document.addEventListener(
+ "mousedown",
+ (e) => {
+ // @ts-expect-error
+ if (threeRenderer.domElement.contains(e.target)) {
+ if (isLocked) {
+ postMouseData(e, true);
+ } else if (isPointerLockEnabled) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mouseup",
+ (e) => {
if (isLocked) {
- postMouseData(e, true);
- } else if (isPointerLockEnabled) {
+ postMouseData(e, false);
+ // @ts-expect-error
+ } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
threeRenderer.domElement.requestPointerLock();
}
+ },
+ true
+ );
+ document.addEventListener(
+ "mousemove",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e);
+ }
+ },
+ true
+ );
+
+ document.addEventListener("pointerlockchange", () => {
+ isLocked = document.pointerLockElement === threeRenderer.domElement;
+ });
+ document.addEventListener("pointerlockerror", (e) => {
+ console.error("Pointer lock error", e);
+ });
+
+ const oldStep = vm.runtime._step;
+ vm.runtime._step = function(...args) {
+ const ret = oldStep.call(this, ...args);
+ if (isPointerLockEnabled) {
+ const {
+ width,
+ height
+ } = rect;
+ mouse._clientX = width / 2;
+ mouse._clientY = height / 2;
+ mouse._scratchX = 0;
+ mouse._scratchY = 0;
}
- },
- true
- );
- document.addEventListener(
- "mouseup",
- (e) => {
- if (isLocked) {
- postMouseData(e, false);
- // @ts-expect-error
- } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
- threeRenderer.domElement.requestPointerLock();
- }
- },
- true
- );
- document.addEventListener(
- "mousemove",
- (e) => {
+ return ret;
+ };
+
+ vm.runtime.on("PROJECT_LOADED", () => {
+ isPointerLockEnabled = false;
if (isLocked) {
- postMouseData(e);
+ document.exitPointerLock();
}
- },
- true
- );
-
- document.addEventListener("pointerlockchange", () => {
- isLocked = document.pointerLockElement === threeRenderer.domElement;
- });
- document.addEventListener("pointerlockerror", (e) => {
- console.error("Pointer lock error", e);
- });
-
- const oldStep = vm.runtime._step;
- vm.runtime._step = function (...args) {
- const ret = oldStep.call(this, ...args);
- if (isPointerLockEnabled) {
- const { width, height } = rect;
- mouse._clientX = width / 2;
- mouse._clientY = height / 2;
- mouse._scratchX = 0;
- mouse._scratchY = 0;
- }
- return ret;
- };
+ });
- vm.runtime.on("PROJECT_LOADED", () => {
- isPointerLockEnabled = false;
- if (isLocked) {
- document.exitPointerLock();
- }
- });
-
- class Pointerlock {
- getInfo() {
- return {
- id: "threepointerlockmod",
- name: "Pointerlock for Extra 3D",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setLocked", blockType: Scratch.BlockType.COMMAND, text: "set pointer lock [enabled]", arguments: { enabled: { type: Scratch.ArgumentType.STRING, defaultValue: "true", menu: "enabled"}},},
- {opcode: "isLocked", blockType: Scratch.BlockType.BOOLEAN, text: "pointer locked?",},
- ],
- menus: {
- enabled: {acceptReporters: true, items: [
- {text: "enabled", value: "true"},{text: "disabled", value: "false"},
- ]}
- },
+ class Pointerlock {
+ getInfo() {
+ return {
+ id: "threepointerlockmod",
+ name: "Pointerlock for Extra 3D",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setLocked",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set pointer lock [enabled]",
+ arguments: {
+ enabled: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ menu: "enabled",
+ },
+ },
+ },
+ {
+ opcode: "isLocked",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "pointer locked?",
+ },
+ ],
+ menus: {
+ enabled: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "true",
+ },
+ {
+ text: "disabled",
+ value: "false",
+ },
+ ],
+ },
+ },
+ };
}
- }
- setLocked(args) {
- isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
- if (!isPointerLockEnabled && isLocked) {
- document.exitPointerLock();
+ setLocked(args) {
+ isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
+ if (!isPointerLockEnabled && isLocked) {
+ document.exitPointerLock();
+ }
}
- }
- isLocked() {
- return isLocked;
+ isLocked() {
+ return isLocked;
+ }
}
- }
-Scratch.extensions.register(new Pointerlock())
-
- })
-
-
-
-
+ Scratch.extensions.register(new Pointerlock());
+ });
})(Scratch);
diff --git a/threejsD.js.orig b/threejsD.js.orig
new file mode 100644
index 0000000..d1db442
--- /dev/null
+++ b/threejsD.js.orig
@@ -0,0 +1,5029 @@
+/* jshint esversion: 11 */
+// Name: Extra 3D
+// ID: threejsExtension
+// Description: Use three js inside Turbowarp! A 3D graphics library.
+// By: Civero
+// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
+
+(function(Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Three-D extension must run unsandboxed");
+ }
+
+ if (Scratch.vm.runtime.isPackaged) {
+ alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
+ return;
+ }
+ //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
+
+ const vm = Scratch.vm;
+ const runtime = vm.runtime;
+ const renderer = Scratch.renderer;
+ const canvas = renderer.canvas;
+ const Cast = Scratch.Cast;
+ const menuIconURI =
+ "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
+
+ let alerts = false;
+ console.log("alerts are " + (alerts ? "enabled" : "disabled"));
+
+ let isMouseDown = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+ let prevMouse = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+
+ let lastWidth = 0;
+ let lastHeight = 0;
+
+ let THREE;
+ let clock;
+ let running;
+ let loopId;
+ //Addons
+ let GLTFLoader;
+ let gltf;
+ let OrbitControls;
+ let controls;
+ let BufferGeometryUtils;
+ let TextGeometry;
+ let fontLoad;
+ //Physics
+ let RAPIER;
+ let physicsWorld;
+
+ let threeRenderer;
+ let scene;
+ let camera;
+ let eulerOrder = "YXZ";
+
+ let composer;
+ let passes = {};
+ let customEffects = [];
+ let renderTargets = {};
+
+ let materials = {};
+ let geometries = {};
+ let lights = {};
+ let models = {};
+
+ let assets = {
+ //should i place materials, geometries; inside too?
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {}, //not the same as the global one! this one only stores textures
+ };
+
+ let raycastResult = [];
+
+ function resetor(level) {
+ camera = undefined;
+ composer.reset();
+
+ passes = {};
+ customEffects = [];
+ renderTargets = {};
+
+ materials = {};
+ geometries = {};
+ lights = {};
+ models = {};
+
+ if (level > 0) {
+ assets = {
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {},
+ };
+ }
+
+ updateComposers();
+ }
+
+ //utility
+ function vector3ToString(prop) {
+ if (!prop) return "0,0,0";
+
+ const x =
+ typeof prop.x === "number" ?
+ prop.x :
+ typeof prop._x === "number" ?
+ prop._x :
+ JSON.stringify(prop).includes("X") ?
+ prop :
+ 0;
+ const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
+ const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
+
+ return [x, y, z];
+ }
+
+ //objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true);
+ if (object) {
+ removeObject(name);
+ alerts ? alert(name + " already exsisted, will replace!") : null;
+ }
+ content.name = name;
+ content.rotation._order = eulerOrder;
+ parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+ content.physics = false;
+
+ object.add(content);
+ }
+
+ function removeObject(name) {
+ let object = getObject(name);
+ if (!object) return;
+
+ scene.remove(object);
+
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true);
+ physicsWorld.removeRigidBody(object.rigidBody, true);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ if (object.isLight) {
+ delete lights[name];
+ }
+ }
+
+ function getObject(name, isNew) {
+ let object = null;
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
+ return;
+ }
+ object = scene.getObjectByName(name);
+ if (!object && !isNew) {
+ alerts ? alert(name + " does not exist! Add it to scene") : null;
+ return;
+ }
+ return object;
+ }
+
+ //materials
+ function encodeCostume(name) {
+ if (name.startsWith("data:image/")) return name;
+ return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ }
+
+ function setTexutre(texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace;
+
+ if (mode === "Pixelate") {
+ texture.minFilter = THREE.NearestFilter;
+ texture.magFilter = THREE.NearestFilter;
+ } else {
+ //Blur
+ texture.minFilter = THREE.NearestMipmapLinearFilter;
+ texture.magFilter = THREE.NearestMipmapLinearFilter;
+ }
+
+ if (style === "Repeat") {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(x, y);
+ }
+
+ texture.generateMipmaps = true;
+ }
+ async function resizeImageToSquare(uri, size = 256) {
+ return new Promise((resolve) => {
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = size;
+ canvas.height = size;
+ const ctx = canvas.getContext("2d");
+
+ // clear + draw image scaled to fit canvas
+ ctx.clearRect(0, 0, size, size);
+ ctx.drawImage(img, 0, 0, size, size);
+
+ resolve(canvas.toDataURL()); // return normalized Data URI
+ //delete canvas?
+ };
+ img.src = uri;
+ });
+ }
+ //light
+ function updateShadowFrustum(light, focusPos) {
+ if (light.type !== "DirectionalLight") return;
+
+ // Frustum Size - Increase this value to cover a larger area.
+ const d = 50;
+
+ // Update Orthographic Shadow Camera Frustum
+ const shadowCamera = light.shadow.camera;
+
+ // Set the width/height of the frustum
+ shadowCamera.left = -d;
+ shadowCamera.right = d;
+ shadowCamera.top = d;
+ shadowCamera.bottom = -d;
+
+ // Determine ranges
+ shadowCamera.near = 0.1;
+ shadowCamera.far = 500;
+
+ // Position the Light and its Target
+ light.target.position.copy(focusPos);
+ const direction = light.position.clone().sub(light.target.position).normalize();
+ light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
+
+ // Ensure matrices are updated.
+ light.target.updateMatrixWorld();
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
+ //composer
+ function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
+
+ // always recreate the RenderPass to point to the current scene/camera
+ passes["Render"] = new RenderPass(scene, camera);
+
+ // ensure composer has a RenderPass as the first pass
+ const hasRender = composer.passes.some((p) => p && p.scene);
+ if (!hasRender) composer.addPass(passes["Render"]);
+ else {
+ // if composer already has one, replace it so it references current scene/camera
+ const idx = composer.passes.findIndex((p) => p && p.scene);
+ composer.passes[idx] = passes["Render"];
+ }
+ }
+ //utility
+ function getMouseNDC(event) {
+ // Use threeRenderer.domElement for correct offset
+ const rect = threeRenderer.domElement.getBoundingClientRect();
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ return [x, y];
+ }
+
+ function checkCanvasSize() {
+ const {
+ width,
+ height
+ } = canvas;
+ if (width !== lastWidth || height !== lastHeight) {
+ lastWidth = width;
+ lastHeight = height;
+ resize();
+ }
+ requestAnimationFrame(checkCanvasSize); //rerun next frame
+ }
+ //physics
+ function computeWorldBoundingBox(mesh) {
+ // Create a Box3 in world coordinates
+ const box = new THREE.Box3().setFromObject(mesh);
+ const size = new THREE.Vector3();
+ box.getSize(size);
+ const center = new THREE.Vector3();
+ box.getCenter(center);
+ return {
+ size,
+ center,
+ };
+ }
+
+ function createCuboidCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
+ return collider;
+ }
+
+ function createBallCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ // radius = 1/2 of the largest verticie
+ const radius = Math.max(size.x, size.y, size.z) / 2;
+ const collider = RAPIER.ColliderDesc.ball(radius);
+ return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
+ }
+
+ function createConvexHullCollider(mesh) {
+ mesh.updateWorldMatrix(true, false);
+
+ const position = mesh.geometry.attributes.position;
+ const vertices = [];
+ const vertex = new THREE.Vector3();
+
+ // Matrix for scale only
+ const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
+
+ for (let i = 0; i < position.count; i++) {
+ vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
+ vertices.push(vertex.x, vertex.y, vertex.z);
+ }
+
+ const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
+ return collider;
+ }
+
+ function TriMesh(mesh) {
+ // Get the positions array (from your geoPoints function)
+ const positions = mesh.geometry.attributes.position.array;
+ const numVertices = positions.length / 3;
+
+ // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
+ const indices = Array.from({
+ length: numVertices,
+ },
+ (_, i) => i
+ );
+
+ const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
+
+ return collider;
+ }
+
+ function getModel(model, name) {
+ const file = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === model);
+ if (!file) return;
+
+ return new Promise((resolve, reject) => {
+ gltf.parse(
+ file.asset.data.buffer,
+ "",
+ (gltf) => {
+ const root = gltf.scene;
+ root.traverse((child) => {
+ if (child.isMesh) {
+ child.castShadow = true;
+ child.receiveShadow = true;
+ }
+ });
+
+ const mixer = new THREE.AnimationMixer(root);
+ const actions = {};
+ gltf.animations.forEach((clip) => {
+ const act = mixer.clipAction(clip);
+ act.clampWhenFinished = true;
+ actions[clip.name] = act;
+ });
+
+ models[name] = {
+ root,
+ mixer,
+ actions,
+ };
+ resolve(root);
+ },
+ (error) => {
+ console.error("Error parsing GLB model:", error);
+ reject(error);
+ }
+ );
+ });
+ }
+ async function openFileExplorer(format) {
+ return new Promise((resolve) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = format;
+ input.multiple = false;
+ input.onchange = () => {
+ resolve(input.files);
+ input.remove();
+ };
+ input.click();
+ });
+ }
+
+ function getMeshesUsingTexture(scene, targetTexture) {
+ const meshes = [];
+
+ scene.traverse((object) => {
+ if (object.material) {
+ const materials = Array.isArray(object.material) ? object.material : [object.material];
+ for (const material of materials) {
+ if (material.map === targetTexture) {
+ meshes.push(object);
+ break;
+ }
+ }
+ }
+ });
+
+ return meshes;
+ }
+
+ function getAsset(path) {
+ if (typeof path == "string") {
+ //string?
+ if (path.includes("/")) {
+ //has the /?
+ const value = path.split("/");
+ console.log(value[0], value[1]);
+ return assets[value[0]][value[1]];
+ }
+ }
+
+ return JSON.parse(path); //boolean or number
+ }
+
+ let mouseNDC = [0, 0];
+ //loops/init
+ function stopLoop() {
+ if (!running) return;
+ running = false;
+
+ if (loopId) {
+ cancelAnimationFrame(loopId);
+ loopId = null;
+ if (threeRenderer) threeRenderer.clear();
+ }
+ }
+ async function load() {
+ if (!THREE) {
+ // @ts-ignore
+<<<<<<< HEAD
+ THREE = await import("https://esm.sh/three@0.180.0")
+ window._THREE_ = THREE
+=======
+ THREE = await import("https://esm.sh/three@0.180.0");
+>>>>>>> e4a038b (Update threejsD.js)
+ //Addons
+ GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
+ OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
+ BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
+ TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
+ const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
+ fontLoad = new FontLoader.FontLoader();
+
+ const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
+ const {
+ EffectComposer,
+ EffectPass,
+ RenderPass,
+
+ Effect,
+ BloomEffect,
+ GodRaysEffect,
+ DotScreenEffect,
+ DepthOfFieldEffect,
+
+ BlendFunction,
+ } = POSTPROCESSING;
+ //so i can use them later as global
+ window.EffectComposer = EffectComposer;
+ window.EffectPass = EffectPass;
+ window.RenderPass = RenderPass;
+ window.Effect = Effect;
+ window.BloomEffect = BloomEffect;
+ window.GodRaysEffect = GodRaysEffect;
+ window.DotScreenEffect = DotScreenEffect;
+ window.DepthOfFieldEffect = DepthOfFieldEffect;
+ window.BlendFunction = BlendFunction;
+
+ RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
+ await RAPIER.init();
+
+ threeRenderer = new THREE.WebGLRenderer({
+ powerPreference: "high-performance",
+ antialias: false,
+ stencil: false,
+ depth: true,
+ });
+ threeRenderer.setPixelRatio(window.devicePixelRatio);
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
+ //threeRenderer.toneMappingExposure = 1.0 //(test)
+
+ threeRenderer.shadowMap.enabled = true;
+ threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
+ threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
+
+ gltf = new GLTFLoader.GLTFLoader();
+ clock = new THREE.Clock();
+
+ // Example: create a composer
+ composer = new EffectComposer(threeRenderer, {
+ frameBufferType: THREE.HalfFloatType,
+ });
+
+ renderer.addOverlay(threeRenderer.domElement, "manual");
+ renderer.addOverlay(canvas, "manual");
+ renderer.setBackgroundColor(1, 1, 1, 0);
+
+ resize();
+
+ window.addEventListener("mousedown", (e) => {
+ if (e.button === 0) isMouseDown.left = true;
+ if (e.button === 1) isMouseDown.middle = true;
+ if (e.button === 2) isMouseDown.right = true;
+ });
+ window.addEventListener("mouseup", (e) => {
+ if (e.button === 0) isMouseDown.left = false;
+ prevMouse.left = false;
+ if (e.button === 1) isMouseDown.middle = false;
+ prevMouse.middle = false;
+ if (e.button === 2) isMouseDown.right = false;
+ prevMouse.right = false;
+ });
+ // prevent contextmenu on right click
+ threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
+
+ threeRenderer.domElement.addEventListener("mousemove", (event) => {
+ mouseNDC = getMouseNDC(event);
+ });
+
+ running = false;
+ load();
+
+<<<<<<< HEAD
+ startRenderLoop()
+ runtime.on('PROJECT_START', () => startRenderLoop())
+ runtime.on('PROJECT_STOP_ALL', () => stopLoop())
+ runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
+ checkCanvasSize()
+=======
+ startRenderLoop();
+ runtime.on("PROJECT_START", () => startRenderLoop());
+ runtime.on("PROJECT_STOP_ALL", () => stopLoop());
+ runtime.on("STAGE_SIZE_CHANGED", () => {
+ requestAnimationFrame(() => resize());
+ });
+ //if (!runtime.isPackaged) checkCanvasSize() //only in editor
+>>>>>>> e4a038b (Update threejsD.js)
+ }
+ }
+
+ function startRenderLoop() {
+ if (running) return;
+ running = true;
+
+ const loop = () => {
+ if (!running) return;
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step();
+
+ scene.children.forEach((obj) => {
+ if (!obj.isMesh || !obj.physics) return;
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation());
+ obj.quaternion.copy(obj.rigidBody.rotation());
+ }
+ });
+ }
+ if (scene && camera) {
+ if (controls) controls.update();
+
+ const delta = clock.getDelta();
+ Object.values(models).forEach((model) => {
+ if (model) model.mixer.update(delta);
+ });
+
+ Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
+
+ //update custom effects time
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("time")) {
+ e.uniforms.get("time").value += delta;
+ }
+ });
+ Object.values(renderTargets).forEach((t) => {
+ if (t.camera.type == "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height;
+ t.camera.updateProjectionMatrix();
+ }
+ // get meshes using the texture associated with this target
+ const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = false;
+ });
+
+ if (t.camera.type == "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target);
+ threeRenderer.clear(true, true, true);
+ threeRenderer.render(scene, t.camera);
+ } else {
+ t.target.clear(threeRenderer);
+ t.camera.update(threeRenderer, scene); //cubeCamera
+ }
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = true;
+ });
+ });
+
+ camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
+ camera.updateProjectionMatrix();
+ threeRenderer.setRenderTarget(null);
+ composer.render(delta);
+ }
+
+ loopId = requestAnimationFrame(loop);
+ };
+
+ loopId = requestAnimationFrame(loop);
+ }
+
+ function resize() {
+ const w = canvas.width;
+ const h = canvas.height;
+
+ threeRenderer.setSize(w, h);
+ composer.setSize(w, h);
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("resolution")) {
+ e.uniforms.get("resolution").value.set(w, h);
+ }
+ });
+
+ if (camera) {
+ camera.aspect = w / h;
+ camera.updateProjectionMatrix();
+ }
+ }
+ //wait until all packages are loaded
+ Promise.resolve(load()).then(() => {
+ class threejsExtension {
+ getInfo() {
+ return {
+ id: "threejsExtension",
+ name: "Extra 3D",
+ color1: "#222222",
+ color2: "#222222",
+ color3: "#11cc99",
+ menuIconURI,
+ blockIconURI: menuIconURI,
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Show Docs",
+ func: "openDocs",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Toggle Alerts",
+ func: "alerts",
+ },
+ ],
+ menus: {},
+ };
+ }
+ openDocs() {
+ open("https://civ3ro.github.io/extensions/Documentation/");
+ }
+ alerts() {
+ alerts = !alerts;
+ alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
+ }
+ }
+ Scratch.extensions.register(new threejsExtension());
+
+ class ThreeRenderer {
+ getInfo() {
+ return {
+ id: "threeRenderer",
+ name: "Three Renderer",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setRendererRatio",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Pixel Ratio to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "eulerOrder",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set euler order to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "YXZ",
+ },
+ },
+ },
+ ],
+ menus: {},
+ };
+ }
+
+ setRendererRatio(args) {
+ threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
+ }
+ eulerOrder(args) {
+ eulerOrder = args.VALUE;
+ console.log("euler order set to", eulerOrder);
+ }
+ }
+ Scratch.extensions.register(new ThreeRenderer());
+
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
+ getInfo() {
+ return {
+ id: "threeScene",
+ name: "Three Scene",
+ color1: "#4638c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newScene",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new Scene [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ },
+ },
+
+ {
+ opcode: "setSceneProperty",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Scene [PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneProperties",
+ defaultValue: "background",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "getSceneObjects",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get Scene [THING]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneThings",
+ },
+ },
+ },
+ {
+ opcode: "reset",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Reset Everything",
+ },
+ ],
+ menus: {
+ sceneProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Background",
+ value: "background",
+ },
+ {
+ text: "Background Blurriness",
+ value: "backgroundBlurriness",
+ },
+ {
+ text: "Background Intensity",
+ value: "backgroundIntensity",
+ },
+ {
+ text: "Background Rotation",
+ value: "backgroundRotation",
+ },
+ {
+ text: "Environment",
+ value: "environment",
+ },
+ {
+ text: "Environment Intensity",
+ value: "environmentIntensity",
+ },
+ {
+ text: "Environment Rotation",
+ value: "environmentRotation",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ ],
+ },
+ sceneThings: {
+ acceptReporters: false,
+ items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
+ },
+ },
+ };
+ }
+
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME;
+ scene.background = new THREE.Color("#222");
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {
+ ...this.scenes,
+ ...scene,
+ };
+ resetor(0);
+ }
+
+ reset() {
+ resetor(1);
+ }
+
+ async setSceneProperty(args) {
+ const property = args.PROPERTY;
+ const value = getAsset(args.VALUE);
+
+ scene[property] = value;
+ }
+ getSceneObjects(args) {
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse((obj) => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ else if (args.THING === "Scene Properties") {
+ console.log(scene);
+ return "check console";
+ } else if (args.THING === "Other assets") return JSON.stringify(assets);
+
+ return JSON.stringify(names); // if objects
+ }
+ }
+ Scratch.extensions.register(new ThreeScene());
+
+ class ThreeCameras {
+ getInfo() {
+ return {
+ id: "threeCameras",
+ name: "Three Cameras",
+ color1: "#38c59bff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add camera [TYPE] [CAMERA] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraTypes",
+ },
+ },
+ },
+ {
+ opcode: "setCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "0.1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "getCamera",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get camera [PROPERTY] of [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "renderSceneCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rendering camera to [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "cubeCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "cubeCamera",
+ },
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "renderTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set a RenderTarget: [RT] for camera [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "sizeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set RenderTarget [RT] size to [W] [H]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ W: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 480,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 360,
+ },
+ },
+ },
+ {
+ opcode: "getTarget",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get RenderTarget: [RT] texture",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "removeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove RenderTarget: [RT]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ ],
+ menus: {
+ cameraTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Perspective",
+ value: "PerspectiveCamera",
+ }, ],
+ },
+ cameraProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Near",
+ value: "near",
+ },
+ {
+ text: "Far",
+ value: "far",
+ },
+ {
+ text: "FOV",
+ value: "fov",
+ },
+ {
+ text: "Focus (nothing...)",
+ value: "focus",
+ },
+ {
+ text: "Zoom",
+ value: "zoom",
+ },
+ ],
+ },
+ },
+ };
+ }
+ addCamera(args) {
+ let v2 = new THREE.Vector2();
+ threeRenderer.getSize(v2);
+ const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
+ object.position.z = 3;
+
+ createObject(args.CAMERA, object, args.GROUP);
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA);
+ object[args.PROPERTY] = args.VALUE;
+ object.updateProjectionMatrix();
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA);
+ const value = JSON.stringify(object[args.PROPERTY]);
+ return value;
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA);
+ if (!object) return;
+ camera = object;
+ //reset composer, else it does not update.
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
+
+ cubeCamera(args) {
+ // Create cube render target
+ const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
+ generateMipmaps: true,
+ });
+ // Create cube camera
+ const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
+ createObject(args.CAMERA, cubeCamera, args.GROUP);
+
+ renderTargets[args.RT] = {
+ target: cubeRenderTarget,
+ camera: cubeCamera,
+ };
+ assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
+ }
+
+ renderTarget(args) {
+ let object = getObject(args.CAMERA);
+ const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
+ generateMipmaps: false,
+ });
+
+ renderTargets[args.RT] = {
+ target: renderTarget,
+ camera: object,
+ };
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H);
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture;
+ console.log(t, renderTargets[args.RT]);
+ return `renderTargets/${t.uuid}`;
+ }
+ removeTarget(args) {
+ delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
+ renderTargets[args.RT].target.dispose();
+ delete renderTargets[args.RT];
+ }
+ }
+ Scratch.extensions.register(new ThreeCameras());
+
+ class ThreeObjects {
+ getInfo() {
+ return {
+ id: "threeObjects",
+ name: "Three Objects",
+ color1: "#38c567ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add object [OBJECT3D] [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "cloneObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myClone",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "setObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "getObject",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of object [OBJECT3D]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ },
+ },
+ {
+ opcode: "objectE",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there an object [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "removeObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove object [OBJECT3D] from scene",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: " ↳ Transforms",
+ },
+ {
+ opcode: "setObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
+ //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {
+ opcode: "getObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of [OBJECT3D]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Materials",
+ },
+ {
+ opcode: "newMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new material [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialTypes",
+ defaultValue: "MeshStandardMaterial",
+ },
+ },
+ },
+ {
+ opcode: "materialE",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a material [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "removeMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove material [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "setMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [PROPERTY] of [NAME] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialProperties",
+ defaultValue: "color",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "setBlending",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] blending to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ },
+ },
+ },
+ {
+ opcode: "setDepth",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] depth to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "depthModes",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Geometries",
+ },
+ {
+ opcode: "newGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new geometry [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "geometryTypes",
+ defaultValue: "BoxGeometry",
+ },
+ },
+ },
+ {
+ opcode: "geometryE",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a geometry [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "removeGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "newGeo",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new empty geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoPoints",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] vertex points to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoUVs",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] UVs to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[UVs]",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "splines",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create spline [NAME] from curve [CURVE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "splineModel",
+ extensions: ["colours_operators"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ MODEL: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ SPACING: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Convert font to JSON",
+ func: "openConv",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load JSON font file",
+ func: "loadFont",
+ },
+ {
+ opcode: "text",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myText",
+ },
+ TEXT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "C-369",
+ },
+ FONT: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "fonts",
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ D: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ CS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 6,
+ },
+ },
+ },
+ ],
+ menus: {
+ objectVector3: {
+ acceptReporters: false,
+ items: [{
+ text: "Positon",
+ value: "position",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Facing Direction (.up)",
+ value: "up",
+ },
+ ],
+ },
+ objectProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Geometry",
+ value: "geometry",
+ },
+ {
+ text: "Material",
+ value: "material",
+ },
+ {
+ text: "Visible (true/false)",
+ value: "visible",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh",
+ value: "Mesh",
+ },
+ {
+ text: "Sprite",
+ value: "Sprite",
+ },
+ {
+ text: "Points",
+ value: "Points",
+ },
+ {
+ text: "Line",
+ value: "Line",
+ },
+ {
+ text: "Group",
+ value: "Group",
+ },
+ ],
+ },
+ XYZ: {
+ acceptReporters: false,
+ items: [{
+ text: "X",
+ value: "x",
+ },
+ {
+ text: "Y",
+ value: "y",
+ },
+ {
+ text: "Z",
+ value: "z",
+ },
+ ],
+ },
+ materialProperties: {
+ acceptReporters: false,
+ items: [
+ "|GENERAL| <-- not a property",
+ {
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map",
+ value: "map",
+ },
+ {
+ text: "Opacity",
+ value: "opacity",
+ },
+ {
+ text: "Transparent",
+ value: "transparent",
+ },
+ {
+ text: "Alpha Map",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test",
+ value: "alphaTest",
+ },
+ {
+ text: "Depth Test",
+ value: "depthTest",
+ },
+ {
+ text: "Depth Write",
+ value: "depthWrite",
+ },
+ {
+ text: "Color Write",
+ value: "colorWrite",
+ },
+ {
+ text: "Side",
+ value: "side",
+ },
+ {
+ text: "Visible",
+ value: "visible",
+ },
+ /*
+ { text: "Blending", value: "blending" },
+ { text: "Blend Src", value: "blendSrc" },
+ { text: "Blend Dst", value: "blendDst" },
+ { text: "Blend Equation", value: "blendEquation" },
+ { text: "Blend Src Alpha", value: "blendSrcAlpha" },
+ { text: "Blend Dst Alpha", value: "blendDstAlpha" },
+ { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
+ {
+ text: "Blend Aplha",
+ value: "blendAplha",
+ },
+ {
+ text: "Blend Color",
+ value: "blendColor",
+ },
+ {
+ text: "Alpha Hash",
+ value: "alphaHash",
+ },
+ {
+ text: "Premultiplied Alpha",
+ value: "premultipliedAlpha",
+ },
+
+ {
+ text: "Tone Mapped",
+ value: "toneMapped",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ {
+ text: "Flat Shading",
+ value: "flatShading",
+ },
+
+ "|MESH Standard / Physical| <-- not a property",
+ {
+ text: "Metalness",
+ value: "metalness",
+ },
+ {
+ text: "Metalness Map",
+ value: "metalnessMap",
+ },
+ {
+ text: "Roughness",
+ value: "roughness",
+ },
+ {
+ text: "Reflectivity",
+ value: "reflectivity",
+ },
+ {
+ text: "Roughness Map",
+ value: "roughnessMap",
+ },
+ {
+ text: "Emissive",
+ value: "emissive",
+ },
+ {
+ text: "Emissive Intensity",
+ value: "emissiveIntensity",
+ },
+ {
+ text: "Emissive Map",
+ value: "emissiveMap",
+ },
+ {
+ text: "Env Map",
+ value: "envMap",
+ },
+ {
+ text: "Env Map Intensity",
+ value: "envMapIntensity",
+ },
+ {
+ text: "Env Map Rotation",
+ value: "envMapRotation",
+ },
+ {
+ text: "Ior",
+ value: "ior",
+ },
+ {
+ text: "Refraction Ratio",
+ value: "refractionRatio",
+ },
+ {
+ text: "Clearcoat",
+ value: "clearcoat",
+ },
+ {
+ text: "Clearcoat Map",
+ value: "clearcoatMap",
+ },
+ {
+ text: "Clearcoat Roughness",
+ value: "clearcoatRoughness",
+ },
+ {
+ text: "Clearcoat Roughness Map",
+ value: "clearcoatRoughnessMap",
+ },
+ {
+ text: "Dispersion",
+ value: "dispersion",
+ },
+ {
+ text: "Sheen",
+ value: "sheen",
+ },
+ {
+ text: "Sheen Color",
+ value: "sheenColor",
+ },
+ {
+ text: "Sheen Color Map",
+ value: "sheenColorMap",
+ },
+ {
+ text: "Sheen Roughness",
+ value: "sheenRoughness",
+ },
+ {
+ text: "Sheen Roughness Map",
+ value: "sheenRoughnessMap",
+ },
+ {
+ text: "Specular Color",
+ value: "specularColor",
+ },
+ {
+ text: "Specular Color Map",
+ value: "specularColorMap",
+ },
+ {
+ text: "Specular Intensity",
+ value: "specularIntensity",
+ },
+ {
+ text: "Specular Intensity Map",
+ value: "specularIntensityMap",
+ },
+ {
+ text: "Transmission",
+ value: "transmission",
+ },
+ {
+ text: "Transmission Map",
+ value: "transmissionMap",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Thickness Map",
+ value: "thicknessMap",
+ },
+ {
+ text: "Anisotropy",
+ value: "anisotropy",
+ },
+ {
+ text: "Anisotropy Map",
+ value: "anisotropyMap",
+ },
+ {
+ text: "Anisotropy Rotation",
+ value: "anisotropyRotation",
+ },
+ {
+ text: "Attenuation Distance",
+ value: "attenuationDistance",
+ },
+ {
+ text: "Attenuation Color",
+ value: "attenuationColor",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Iridescence",
+ value: "iridescence",
+ },
+ {
+ text: "Iridescence Ior",
+ value: "iridescenceIOR",
+ },
+ {
+ text: "Iridescence Map",
+ value: "iridescenceMap",
+ },
+ {
+ text: "Iridescence Thickness Range",
+ value: "iridescenceThicknessRange",
+ },
+
+ "|MESH Displacement / Normal / Bump| <-- not a property",
+ {
+ text: "Displacement Map",
+ value: "displacementMap",
+ },
+ {
+ text: "Displacement Scale",
+ value: "displacementScale",
+ },
+ {
+ text: "Displacement Bias",
+ value: "displacementBias",
+ },
+ {
+ text: "Bump Map",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ {
+ text: "Normal Map Type",
+ value: "normalMapType",
+ },
+
+ "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
+ {
+ text: "Shininess",
+ value: "shininess",
+ },
+
+ {
+ text: "Wireframe",
+ value: "wireframe",
+ },
+ {
+ text: "Wireframe Linewidth",
+ value: "wireframeLinewidth",
+ },
+ {
+ text: "Wireframe Linecap",
+ value: "wireframeLinecap",
+ },
+ {
+ text: "Wireframe Linejoin",
+ value: "wireframeLinejoin",
+ },
+
+ "|POINTS| <-- not a property",
+ {
+ text: "Size",
+ value: "size",
+ },
+ {
+ text: "Size Attenuation",
+ value: "sizeAttenuation",
+ },
+
+ "|LINES| <-- not a property",
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Dash Size",
+ value: "dashSize",
+ },
+ {
+ text: "Gap Size",
+ value: "gapSize",
+ },
+
+ "|SPRITES| <-- not a property",
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [{
+ text: "No Blending",
+ value: "NoBlending",
+ },
+ {
+ text: "Normal Blending",
+ value: "NormalBlending",
+ },
+ {
+ text: "Additive Blending",
+ value: "AdditiveBlending",
+ },
+ {
+ text: "Subtractive Blending",
+ value: "SubtractiveBlending",
+ },
+ {
+ text: "Multiply Blending",
+ value: "MultiplyBlending",
+ },
+ {
+ text: "Custom Blending",
+ value: "CustomBlending",
+ },
+ ],
+ },
+ depthModes: {
+ acceptReporters: false,
+ items: [{
+ text: "Never Depth",
+ value: "NeverDepth",
+ },
+ {
+ text: "Always Depth",
+ value: "AlwaysDepth",
+ },
+ {
+ text: "Equal Depth",
+ value: "EqualDepth",
+ },
+ {
+ text: "Less Depth",
+ value: "LessDepth",
+ },
+ {
+ text: "Less Equal Depth",
+ value: "LessEqualDepth",
+ },
+ {
+ text: "Greater Equal Depth",
+ value: "GreaterEqualDepth",
+ },
+ {
+ text: "Greater Depth",
+ value: "GreaterDepth",
+ },
+ {
+ text: "Not Equal Depth",
+ value: "NotEqualDepth",
+ },
+ ],
+ },
+ materialTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh Basic Material",
+ value: "MeshBasicMaterial",
+ },
+ {
+ text: "Mesh Standard Material",
+ value: "MeshStandardMaterial",
+ },
+ {
+ text: "Mesh Physical Material",
+ value: "MeshPhysicalMaterial",
+ },
+ {
+ text: "Mesh Lambert Material",
+ value: "MeshLambertMaterial",
+ },
+ {
+ text: "Mesh Phong Material",
+ value: "MeshPhongMaterial",
+ },
+ {
+ text: "Mesh Depth Material",
+ value: "MeshDepthMaterial",
+ },
+ {
+ text: "Mesh Normal Material",
+ value: "MeshNormalMaterial",
+ },
+ {
+ text: "Mesh Matcap Material",
+ value: "MeshMatcapMaterial",
+ },
+ {
+ text: "Mesh Toon Material",
+ value: "MeshToonMaterial",
+ },
+ {
+ text: "Line Basic Material",
+ value: "LineBasicMaterial",
+ },
+ {
+ text: "Line Dashed Material",
+ value: "LineDashedMaterial",
+ },
+ {
+ text: "Points Material",
+ value: "PointsMaterial",
+ },
+ {
+ text: "Sprite Material",
+ value: "SpriteMaterial",
+ },
+ {
+ text: "Shadow Material",
+ value: "ShadowMaterial",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ geometryTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box Geometry",
+ value: "BoxGeometry",
+ },
+ {
+ text: "Sphere Geometry",
+ value: "SphereGeometry",
+ },
+ {
+ text: "Cylinder Geometry",
+ value: "CylinderGeometry",
+ },
+ {
+ text: "Plane Geometry",
+ value: "PlaneGeometry",
+ },
+ {
+ text: "Circle Geometry",
+ value: "CircleGeometry",
+ },
+ {
+ text: "Torus Geometry",
+ value: "TorusGeometry",
+ },
+ {
+ text: "Torus Knot Geometry",
+ value: "TorusKnotGeometry",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model! (GLB Loader category)"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ fonts: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".json"));
+ if (models.length < 1) return [
+ ["Load a font!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ addObject(args) {
+ const object = new THREE[args.TYPE]();
+
+ object.castShadow = true;
+ object.receiveShadow = true;
+
+ createObject(args.OBJECT3D, object, args.GROUP);
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D);
+ const clone = object.clone(true);
+ clone.name;
+ createObject(args.NAME, clone, args.GROUP);
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ let values = JSON.parse(args.VALUE);
+
+ function degToRad(deg) {
+ return (deg * Math.PI) / 180;
+ }
+
+ if (object.rigidBody) {
+ const x = values[0];
+ const y = values[1];
+ const z = values[2];
+ if (args.PROPERTY === "rotation") {
+ const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
+ const quaternion = new THREE.Quaternion();
+ quaternion.setFromEuler(euler);
+
+ object.rigidBody.setRotation({
+ x: quaternion.x,
+ y: quaternion.y,
+ z: quaternion.z,
+ w: quaternion.w,
+ });
+ } else if (args.PROPERTY === "position") {
+ object.rigidBody.setTranslation({
+ x: x,
+ y: y,
+ z: z,
+ },
+ true
+ );
+ }
+ return;
+ }
+
+ if (object.isCamera == true && controls) {}
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map((v) => (v * Math.PI) / 180);
+ object.rotation.set(0, 0, 0);
+ }
+ if (object.isDirectionalLight == true) {
+ object.pos = new THREE.Vector3(...values);
+ console.log(true, values, object.pos);
+ return;
+ }
+ object[args.PROPERTY].set(...values);
+
+ if (object.type == "CubeCamera") object.updateCoordinateSystem();
+ }
+ /*
+ changeObjectV3(args) {
+ getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.x += values[0]
+ object.rotation.y += values[1]
+ object.rotation.z += values[2]
+ }
+ else {
+ object[args.PROPERTY].add(...values);
+ }
+ }
+ changeObjectXV3(args) {
+ getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "rotation") value = value * Math.PI / 180
+
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let values = vector3ToString(object[args.PROPERTY]);
+ if (args.PROPERTY === "rotation") {
+ const toDeg = Math.PI / 180;
+ values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
+ }
+
+ return JSON.stringify(values);
+ }
+ setObject(args) {
+ let object = getObject(args.OBJECT3D);
+ let value = args.VALUE;
+ if (args.PROPERTY === "material") {
+ const mat = materials[args.NAME];
+ if (mat) value = mat;
+ else value = undefined;
+ } else if (args.PROPERTY === "geometry") {
+ const geo = geometries[args.NAME];
+ if (geo) value = geo;
+ else value = undefined;
+ } else value = !!value;
+
+ if (value == undefined) return; //invalid geo/mat
+ object[args.PROPERTY] = value;
+ }
+ getObject(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let value;
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value;
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D);
+ }
+ objectE(args) {
+ return scene.children.map((o) => o.name).includes(args.NAME);
+ }
+
+ //defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
+
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
+ const mat = materials[args.NAME];
+
+ let value = args.VALUE;
+
+ if (args.VALUE == "false") value = false;
+
+ if (args.PROPERTY == "side") {
+ value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
+ } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
+ else value = getAsset(value);
+
+ console.log("o:", args.VALUE, typeof args.VALUE);
+ console.log("r:", value, typeof value);
+
+ mat[args.PROPERTY] = await value; //await incase its a texture
+ mat.needsUpdate = true;
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME];
+ mat.blending = THREE[args.VALUE];
+ mat.premultipliedAlpha = true;
+ mat.needsUpdate = true;
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME];
+ mat.depthFunc = THREE[args.VALUE];
+ mat.needsUpdate = true;
+ }
+ removeMaterial(args) {
+ const mat = materials[args.NAME];
+ mat.dispose();
+ delete materials[args.NAME];
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false;
+ }
+
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ const geo = new THREE[args.TYPE]();
+ geo.name = args.NAME;
+
+ geometries[args.NAME] = geo;
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo[args.PROPERTY] = args.VALUE;
+
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo.dispose();
+ delete geometries[args.NAME];
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false;
+ }
+
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry();
+ geometry.name = args.NAME;
+ geometries[args.NAME] = geometry;
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME];
+ const positions = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v3 of each vertex of each triangle
+
+ geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
+ geometry.computeVertexNormals();
+
+ geometry.needsUpdate = true;
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME];
+ const UVs = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v2 of each UV of each triangle
+
+ geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
+ geometry.needsUpdate = true;
+ }
+
+ splines(args) {
+ const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
+ geometry.name = args.NAME;
+
+ geometries[args.NAME] = geometry;
+ }
+
+ async splineModel(args) {
+ const model = await getModel(args.MODEL, args.NAME);
+ if (!model) return console.warn("Model not found:", args.MODEL);
+
+ const curve = getAsset(args.CURVE);
+ const spacing = parseFloat(args.SPACING) || 1;
+ const curveLength = curve.getLength();
+ const divisions = Math.floor(curveLength / spacing);
+
+ const geomList = [];
+ const matList = [];
+
+ for (let i = 0; i <= divisions; i++) {
+ const t = i / divisions;
+ const pos = curve.getPointAt(t);
+ const tangent = curve.getTangentAt(t);
+
+ const temp = model.clone(true);
+ temp.position.copy(pos);
+
+ const up = new THREE.Vector3(0, 0, 1);
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
+ temp.quaternion.copy(quat);
+
+ temp.updateMatrixWorld(true);
+
+ temp.traverse((child) => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone();
+ geom.applyMatrix4(child.matrixWorld);
+ geomList.push(geom);
+ matList.push(child.material); //.clone() ?
+ }
+ });
+ }
+
+ const validGeoms = geomList.filter((g) => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
+ if (!ok) console.warn("geometry skipped:", g);
+ return ok;
+ });
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
+ merged.computeBoundingBox();
+ merged.computeBoundingSphere();
+
+ merged.name = args.NAME;
+ geometries[args.NAME] = merged;
+ matList.name = args.NAME;
+ materials[args.NAME] = matList;
+ }
+
+ async text(args) {
+ const fontFile = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === args.FONT);
+ if (!fontFile) return;
+
+ const json = new TextDecoder().decode(fontFile.asset.data.buffer);
+ const fontData = JSON.parse(json);
+
+ const font = fontLoad.parse(fontData);
+
+ const params = {
+ font: font,
+ size: JSON.parse(args.S),
+ height: JSON.parse(args.D),
+ curveSegments: JSON.parse(args.CS),
+ bevelEnabled: false,
+ };
+ const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
+ geometry.computeVertexNormals();
+ geometry.center(); // optional, recenters the text
+
+ geometry.name = args.NAME;
+
+ geometries[args.NAME] = geometry;
+ }
+
+ async loadFont() {
+ openFileExplorer(".json").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ // From lily's assets
+ // // Thank you PenguinMod for providing this code.
+
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Font loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading font.");
+ }
+
+ // End of PenguinMod
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ openConv() {
+ {
+ open("https://gero3.github.io/facetype.js/");
+ }
+ }
+ }
+ Scratch.extensions.register(new ThreeObjects());
+
+ class ThreeLights {
+ getInfo() {
+ return {
+ id: "threeLights",
+ name: "Three Lights",
+ color1: "#c7a22aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add light [NAME] type [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightTypes",
+ },
+ },
+ },
+ {
+ opcode: "setLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set light [NAME][PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightProperties",
+ defaultValue: "intensity",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ lightTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Ambient Light",
+ value: "AmbientLight",
+ },
+ {
+ text: "Directional Light",
+ value: "DirectionalLight",
+ },
+ {
+ text: "Point Light",
+ value: "PointLight",
+ },
+ {
+ text: "Hemisphere Light",
+ value: "HemisphereLight",
+ },
+ {
+ text: "Spot Light",
+ value: "SpotLight",
+ },
+ ],
+ },
+ lightProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Intensity",
+ value: "intensity",
+ },
+ {
+ text: "Cast Shadow?",
+ value: "castShadow",
+ },
+ {
+ text: "Ground Color (HemisphereLight)",
+ value: "groundColor",
+ },
+ {
+ text: "Map (SpotLight)",
+ value: "map",
+ },
+ {
+ text: "Distance (SpotLight)",
+ value: "distance",
+ },
+ {
+ text: "Decay (SpotLight)",
+ value: "decay",
+ },
+ {
+ text: "Penumbra (SpotLight)",
+ value: "penumbra",
+ },
+ {
+ text: "Angle/Size (SpotLight)",
+ value: "angle",
+ },
+ {
+ text: "Power (SpotLight)",
+ value: "power",
+ },
+ {
+ text: "Target Position (Directional/SpotLight)",
+ value: "target",
+ },
+ ],
+ },
+ },
+ };
+ }
+
+ addLight(args) {
+ const light = new THREE[args.TYPE](0xffffff, 1);
+
+ createObject(args.NAME, light, args.GROUP);
+ lights[args.NAME] = light;
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
+
+ light.castShadow = true;
+ if (light.type === "PointLight") return;
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0);
+ scene.add(light.target);
+
+ light.pos = new THREE.Vector3(0, 0, 0);
+
+ light.shadow.mapSize.width = 4096;
+ light.shadow.mapSize.height = 2048;
+
+ if (light.type === "SpotLight") {
+ light.decay = 0;
+ light.shadow.camera.near = 500;
+ light.shadow.camera.far = 4000;
+ light.shadow.camera.fov = 30;
+ }
+ light.shadow.needsUpdate = true;
+ light.needsUpdate = true;
+ }
+
+ setLight(args) {
+ const light = lights[args.NAME];
+ if (!args.PROPERTY) return;
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)); //vector3
+ light.target.updateMatrixWorld();
+ } else {
+ light[args.PROPERTY] = getAsset(args.VALUE);
+ }
+ light.needsUpdate = true;
+
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
+
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
+ }
+ Scratch.extensions.register(new ThreeLights());
+
+ class ThreeUtilities {
+ getInfo() {
+ return {
+ id: "threeUtility",
+ name: "Three Utilities",
+ color1: "#3875c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newVector2",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ {
+ opcode: "newVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y] [Z]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Z: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "operateV3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "do [V3] [O] [V32]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ O: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "operators",
+ },
+ V32: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "moveVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "move [S] steps in vector [V3] in direction [D3]",
+ arguments: {
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "directionTo",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "direction from [V3] to [T3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ T3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "newColor",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Color [HEX]",
+ arguments: {
+ HEX: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ },
+ },
+ },
+ {
+ opcode: "newFog",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Fog [COLOR] [NEAR] [FAR]",
+ arguments: {
+ COLOR: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ exemptFromNormalization: true,
+ },
+ NEAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ FAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 10,
+ },
+ },
+ },
+ {
+ opcode: "newTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newCubeTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUMEX0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEX1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newEquirectangularTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Equirectangular Texture [COSTUME] [MODE]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "curve",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
+ arguments: {
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "curveTypes",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
+ },
+ CLOSED: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "mouseDown",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "mouse [BUTTON] [action]?",
+ arguments: {
+ BUTTON: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseButtons",
+ },
+ action: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseAction",
+ },
+ },
+ },
+ {
+ opcode: "mousePos",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "mouse position",
+ arguments: {},
+ },
+ "---",
+ {
+ opcode: "getItem",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get item [ITEM] of [ARRAY]",
+ arguments: {
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ ARRAY: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: `["myObject", "myLight"]`,
+ },
+ },
+ },
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Raycasting",
+ },
+ {
+ opcode: "raycast",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Raycast from [V3] in direction [D3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,1]",
+ },
+ },
+ },
+ {
+ opcode: "getRaycast",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get raycast [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "raycastProperties",
+ },
+ },
+ },
+ ],
+ menus: {
+ materialProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map (texture)",
+ value: "map",
+ },
+ {
+ text: "Alpha Map (texture)",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test (0-1)",
+ value: "alphaTest",
+ },
+ {
+ text: "Side (front/back/double)",
+ value: "side",
+ },
+ {
+ text: "Bump Map (texture)",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ raycastProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Intersected Object Names",
+ value: "name",
+ },
+ {
+ text: "Number of Objects",
+ value: "number",
+ },
+ {
+ text: "Intersected Objects distances",
+ value: "distance",
+ },
+ ],
+ },
+ mouseButtons: {
+ acceptReporters: false,
+ items: ["left", "middle", "right"],
+ },
+ mouseAction: {
+ acceptReporters: false,
+ items: ["Down", "Clicked"],
+ },
+ curveTypes: {
+ acceptReporters: false,
+ items: ["CatmullRomCurve3"],
+ },
+ operators: {
+ acceptReporters: false,
+ items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
+ },
+ },
+ };
+ }
+ mouseDown(args) {
+ if (args.action === "Down") return isMouseDown[args.BUTTON];
+ if (args.action === "Clicked") {
+ if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
+ else prevMouse[args.BUTTON] = true;
+ return true;
+ }
+ }
+ mousePos(event) {
+ return JSON.stringify(mouseNDC);
+ }
+ newVector3(args) {
+ return JSON.stringify([args.X, args.Y, args.Z]);
+ }
+ operateV3(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const v32 = new THREE.Vector3(...JSON.parse(args.V32));
+
+ let r;
+ if (args.O == "+") r = v3.add(v32);
+ else if (args.O == "-") r = v3.sub(v32);
+ else if (args.O == "*") r = v3.multiply(v32);
+ else if (args.O == "/") r = v3.divide(v32);
+ else if (args.O == "=") r = v3.equals(v32);
+ else if (args.O == "max") r = v3.max(v32);
+ else if (args.O == "min") r = v3.min(v32);
+ else if (args.O == "dot") r = v3.dot(v32);
+ else if (args.O == "cross") r = v3.cross(v32);
+ else if (args.O == "distance to") r = v3.distanceTo(v32);
+ else if (args.O == "angle to") r = v3.angleTo(v32);
+ else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
+
+ if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
+ else return JSON.stringify(r);
+ }
+
+ newVector2(args) {
+ return JSON.stringify([args.X, args.Y]);
+ }
+
+ moveVector3(args) {
+ const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
+ const steps = Number(args.S);
+
+ const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
+
+ const yaw = THREE.MathUtils.degToRad(yawInputDeg);
+ const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
+ const roll = THREE.MathUtils.degToRad(rollInputDeg);
+
+ const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
+
+ const forwardVector = new THREE.Vector3(0, 0, -1);
+ const direction = forwardVector.applyEuler(euler).normalize();
+
+ const newPos = currentPos.add(direction.multiplyScalar(steps));
+ return JSON.stringify([newPos.x, newPos.y, newPos.z]);
+ }
+
+ directionTo(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
+
+ const direction = toV3.clone().sub(v3).normalize();
+ // Pitch (X)
+ const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
+ // Yaw (Y)
+ const yaw = Math.atan2(direction.x, direction.z);
+
+ // Roll always 0
+ return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
+ }
+
+ newColor(args) {
+ const color = new THREE.Color(args.HEX);
+ const uuid = crypto.randomUUID();
+ assets.colors[uuid] = color;
+ return `colors/${uuid}`;
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
+ const uuid = crypto.randomUUID();
+ assets.fogs[uuid] = fog;
+ return `fogs/${uuid}`;
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newCubeTexture(args) {
+ const uris = [
+ encodeCostume(args.COSTUMEX0),
+ encodeCostume(args.COSTUMEX1),
+ encodeCostume(args.COSTUMEY0),
+ encodeCostume(args.COSTUMEY1),
+ encodeCostume(args.COSTUMEZ0),
+ encodeCostume(args.COSTUMEZ1),
+ ];
+ const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
+
+ texture.name = "CubeTexture" + args.COSTUMEX0;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+ texture.mapping = THREE.EquirectangularReflectionMapping;
+
+ setTexutre(texture, args.MODE);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+
+ curve(args) {
+ function parsePoints(input) {
+ // Match all [x,y,z] groups
+ const matches = input.match(/\[([^\]]+)\]/g);
+ if (!matches) return [];
+
+ return matches.map((str) => {
+ const nums = str
+ .replace(/[\[\]\s]/g, "")
+ .split(",")
+ .map(Number);
+ return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
+ });
+ }
+ const points = parsePoints(args.POINTS);
+ const curve = new THREE[args.TYPE](points);
+ curve.closed = JSON.parse(args.CLOSED);
+
+ const uuid = crypto.randomUUID();
+ assets.curves[uuid] = curve;
+ return `curves/${uuid}`;
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY);
+ const item = items[args.ITEM - 1];
+ if (!item) return "0";
+ return item;
+ }
+
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3));
+ // rotation is in degrees => convert to radians first
+ const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
+
+ const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
+ const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
+
+ const raycaster = new THREE.Raycaster();
+ //const camera = getObject(args.CAMERA)
+ raycaster.set(origin, direction);
+
+ const intersects = raycaster.intersectObjects(scene.children, true);
+
+ raycastResult = intersects;
+ }
+ getRaycast(args) {
+ if (args.PROPERTY === "number") return raycastResult.length;
+ if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
+ return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
+ }
+ }
+ Scratch.extensions.register(new ThreeUtilities());
+
+ class ThreeGLB {
+ getInfo() {
+ return {
+ id: "threeGLB",
+ name: "Three GLB Loader",
+ color1: "#c53838ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load GLB File",
+ func: "loadModelFile",
+ },
+ {
+ opcode: "addModel",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add [ITEM] as [NAME] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ },
+ },
+ {
+ opcode: "getModel",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get object [PROPERTY] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelProperties",
+ },
+ },
+ },
+ {
+ opcode: "playAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "play animation [ANAME] of [NAME], [TIMES] times",
+ arguments: {
+ TIMES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "pauseAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [TOGGLE] animation [ANAME] of [NAME]",
+ arguments: {
+ TOGGLE: {
+ type: Scratch.ArgumentType.NUMBER,
+ menu: "pauseUn",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "stopAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stop animation [ANAME] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ modelProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Animations",
+ value: "animations",
+ }, ],
+ },
+ pauseUn: {
+ acceptReporters: true,
+ items: [{
+ text: "Pause",
+ value: "true",
+ },
+ {
+ text: "Unpasue",
+ value: "false",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ async loadModelFile() {
+ openFileExplorer(".glb").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ {
+ // From lily's assets
+
+ // Thank you PenguinMod for providing this code.
+ {
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ //const res = await Scratch.fetch(args.URL);
+ //const buffer = await res.arrayBuffer();
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Model loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading model.");
+ }
+ }
+ // End of PenguinMod
+ }
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ async addModel(args) {
+ const group = await getModel(args.ITEM, args.NAME);
+
+ createObject(args.NAME, group, args.GROUP);
+ }
+ getModel(args) {
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString();
+ }
+
+ playAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) {
+ console.log("no model!");
+ return;
+ }
+
+ const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
+ if (!action) {
+ console.log("no action!");
+ return;
+ }
+
+ args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
+
+ action.reset().play();
+ }
+ stopAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.stop();
+ }
+ pauseAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.paused = args.TOGGLE;
+ }
+ }
+ Scratch.extensions.register(new ThreeGLB());
+
+ class ThreeAddons {
+ getInfo() {
+ return {
+ id: "threeAddons",
+ name: "Three Addons",
+ color1: "#c538a2ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.LABEL,
+ text: "Orbit Control",
+ },
+ {
+ opcode: "OrbitControl",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set addon Orbit Control [STATE]",
+ arguments: {
+ STATE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "onoff",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "Post Processing",
+ },
+ {
+ opcode: "resetComposer",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset composer",
+ },
+ {
+ opcode: "bloom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ I: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ T: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "godRays",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ DEC: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.95,
+ },
+ DENS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ EXP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ WEI: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.4,
+ },
+ RES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ SAMP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 64,
+ },
+ },
+ },
+ {
+ opcode: "dots",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ A: {
+ type: Scratch.ArgumentType.ANGLE,
+ defaultValue: 0,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "depth",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ FD: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 3,
+ },
+ FL: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.001,
+ },
+ BS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 4,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 240,
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "custom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myShader",
+ },
+ FRA: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ VER: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ ],
+ menus: {
+ onoff: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "1",
+ },
+ {
+ text: "disabled",
+ value: "0",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [
+ "SKIP",
+ "SET",
+ "ADD",
+ "ALPHA",
+ "AVERAGE",
+ "COLOR",
+ "COLOR_BURN",
+ "COLOR_DODGE",
+ "DARKEN",
+ "DIFFERENCE",
+ "DIVIDE",
+ "DST",
+ "EXCLUSION",
+ "HARD_LIGHT",
+ "HARD_MIX",
+ "HUE",
+ "INVERT",
+ "INVERT_RGB",
+ "LIGHTEN",
+ "LINEAR_BURN",
+ "LINEAR_DODGE",
+ "LINEAR_LIGHT",
+ "LUMINOSITY",
+ "MULTIPLY",
+ "NEGATION",
+ "NORMAL",
+ "OVERLAY",
+ "PIN_LIGHT",
+ "REFLECT",
+ "SCREEN",
+ "SRC",
+ "SATURATION",
+ "SOFT_LIGHT",
+ "SUBTRACT",
+ "VIVID_LIGHT",
+ ],
+ },
+ },
+ };
+ }
+
+ OrbitControl(args) {
+ if (controls) controls.dispose();
+
+ console.log("creating...", OrbitControls);
+ controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
+ controls.enableDamping = true;
+
+ controls.enabled = !!args.STATE;
+ console.log(controls);
+ }
+
+ resetComposer() {
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
+
+ bloom(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ bloomEffect.blendMode.opacity.value = args.OP;
+
+ const pass = new EffectPass(camera, bloomEffect);
+
+ composer.addPass(pass);
+ }
+
+ godRays(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ let object = getObject(args.NAME);
+ const sun = object;
+
+ const godRays = new GodRaysEffect(camera, sun, {
+ resolutionScale: args.RES,
+ density: args.DENS, // ray density
+ decay: args.DEC, // fade out
+ weight: args.WEI, // brightness of rays
+ exposure: args.EXP,
+ samples: args.SAMP,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ godRays.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, godRays);
+ composer.addPass(pass);
+ }
+
+ dots(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dot.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, dot);
+ composer.addPass(pass);
+ }
+
+ depth(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dofEffect.blendMode.opacity.value = args.OP;
+
+ const dofPass = new EffectPass(camera, dofEffect);
+ composer.addPass(dofPass);
+ }
+
+ async custom(args) {
+ function cleanGLSL(glslCode) {
+ //delete multilines comments
+ let cleanedCode = glslCode
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
+ .replace(/ /g, "\n")
+ .replace(/\/\/.*$/gm, " ")
+ .replace(/; /g, ";\n");
+
+ return cleanedCode;
+ }
+
+ let fs = cleanGLSL(`
+ ${args.FRA}
+ `);
+ if (!args.FRA.trim()) {
+ fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
+ }
+ const vs = cleanGLSL(`
+ ${args.VER}
+ `);
+ console.log(fs);
+ console.log(vs);
+
+ const effect = new Effect("Custom", fs, {
+ blendFunction: BlendFunction[args.BLEND],
+ vertexShader: vs,
+ uniforms: new Map([
+ //uniforms usually in shaders... open to more!
+ ["time", new THREE.Uniform(0.0)],
+ [
+ "resolution",
+ new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
+ ],
+ ]),
+ defines: new Map([
+ ["USE_TIME", "1"],
+ ["USE_VERTEX_TRANSFORM", ""],
+ ]),
+ });
+
+ effect.blendMode.opacity.value = args.OP;
+
+ const pass = new EffectPass(camera, effect);
+ composer.addPass(pass);
+
+ customEffects.push(effect);
+ }
+ }
+ Scratch.extensions.register(new ThreeAddons());
+
+ class RapierPhysics {
+ getInfo() {
+ return {
+ id: "rapierPhysics",
+ name: "RAPIER Physics",
+ color1: "#222222",
+ color2: "#203024ff",
+ color3: "#78f07eff",
+ blocks: [{
+ opcode: "createWorld",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create world | gravity:[G]",
+ arguments: {
+ G: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,-9.81,0]",
+ },
+ },
+ },
+ {
+ opcode: "getWorld",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get world [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "wProp",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "objectPhysics",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
+ arguments: {
+ state2: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state2",
+ },
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ type: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ defaultValue: "dynamic",
+ },
+ collider: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderTypes",
+ defaultValue: "cuboid",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ mass: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ density: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ friction: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0.5",
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- RigidBody",
+ },
+ {
+ opcode: "setRB",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodySets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getRB",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get rigidbody [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodyProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "lockObjectAxis",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
+ arguments: {
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lockAxes",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Y: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Z: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "addForce",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,10,0]",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "forces",
+ defaultValue: "addForce",
+ },
+ SPACE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "spaces",
+ defaultValue: "world",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "resetForces",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "resetF",
+ defaultValue: "resetForces",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "enableCCD",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable Continuous Collision Detection for [OBJECT] [state]",
+ arguments: {
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "oPropS",
+ defaultValue: "physics",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "fixedJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ RA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ RB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "sphericalJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ },
+ },
+ {
+ opcode: "revoluteJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- Collider",
+ },
+ {
+ opcode: "setC",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderSets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getC",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get collider [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "sensorSingle",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is sensor [SENSOR] touching [OBJECT]?",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "sensorAll",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "objects touching sensor [SENSOR]",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ },
+ },
+ ],
+ menus: {
+ wProp: {
+ acceptReporters: false,
+ items: [{
+ text: "Gravity",
+ value: "gravity",
+ },
+ {
+ text: "log to console",
+ value: "log",
+ },
+ ],
+ },
+ tf: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true",
+ value: "true",
+ },
+ ],
+ },
+ lockAxes: {
+ acceptReporters: false,
+ items: [{
+ text: "Translation",
+ value: "setEnabledTranslations",
+ },
+ {
+ text: "Rotation",
+ value: "setEnabledRotations",
+ },
+ ],
+ },
+ rigidBodyProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Type",
+ value: "bodyType",
+ },
+ {
+ text: "Linear Velocity",
+ value: "linvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "angvel",
+ },
+ {
+ text: "Translation (position)",
+ value: "translation",
+ },
+ {
+ text: "Rotation (quaternion)",
+ value: "rotation",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ //{text: "Center of Mass", value: "centerOfMass"},
+ {
+ text: "Linear Damping",
+ value: "linearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "angularDamping",
+ },
+ {
+ text: "Is Sleeping?",
+ value: "isSleeping",
+ },
+ //{text: "Can Sleep?", value: "isCanSleep"},
+ {
+ text: "Gravity Scale",
+ value: "gravityScale",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ //{text: "Sleeping", value: "sleeping"}
+ ],
+ },
+ rigidBodySets: {
+ acceptReporters: false,
+ items: [
+ //{text: "Linear Velocity", value: "setLinvel"},
+ //{text: "Angular Velocity", value: "setAngvel"},
+ //{text: "Mass", value: "setMass"},
+ {
+ text: "Gravity Scale",
+ value: "setGravityScale",
+ },
+ //{text: "Can Sleep?", value: "setCanSleep"},
+ //{text: "Sleeping", value: "sleeping"},
+ {
+ text: "Linear Damping",
+ value: "setLinearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "setAngularDamping",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ ],
+ },
+ colliderProperties: {
+ acceptReporters: false,
+ items: [
+ //{text: "Collider Type", value: "type"},
+ {
+ text: "Is Sensor?",
+ value: "isSensor",
+ },
+ {
+ text: "Friction",
+ value: "friction",
+ },
+ {
+ text: "Restitution",
+ value: "restitution",
+ },
+ {
+ text: "Density",
+ value: "density",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ {
+ text: "Position",
+ value: "translation",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ //{text: "Area", value: "area"},
+ {
+ text: "Volume",
+ value: "volume",
+ },
+ {
+ text: "Collision Groups",
+ value: "collisionGroups",
+ },
+ //{text: "Collision Mask", value: "collisionMask"},
+ //{text: "Is Enabled?", value: "enabled"},
+ //{text: "Contact Count", value: "contactCount"},
+ //{text: "RigidBody Handle", value: "rigidBody"}
+ ],
+ },
+ colliderSets: {
+ acceptReporters: false,
+ items: [{
+ text: "Friction",
+ value: "setFriction",
+ },
+ {
+ text: "Restitution",
+ value: "setRestitution",
+ },
+ {
+ text: "Density",
+ value: "setDensity",
+ },
+ {
+ text: "Is Sensor?",
+ value: "setSensor",
+ },
+ {
+ text: "Collision Groups",
+ value: "setCollisionGroups",
+ },
+ //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
+ //{text: "Position Offset", value: "setTranslation"},
+ //{text: "Rotation Offset", value: "setRotation"}
+ ],
+ },
+ state: {
+ acceptReporters: true,
+ items: [{
+ text: "on",
+ value: "true",
+ },
+ {
+ text: "off",
+ value: "false",
+ },
+ ],
+ },
+ state2: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true (must be fixed)",
+ value: "true",
+ },
+ ],
+ },
+ spaces: {
+ acceptReporters: false,
+ items: [{
+ text: "World",
+ value: "world",
+ },
+ {
+ text: "Local",
+ value: "local",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Dynamic",
+ value: "dynamic",
+ },
+ {
+ text: "Fixed",
+ value: "fixed",
+ },
+ {
+ text: "Kinematic Position Based",
+ value: "kinematicPositionBased",
+ },
+ ],
+ },
+ colliderTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box, Rectangle, cuboid",
+ value: "cuboid",
+ },
+ {
+ text: "Sphere, ball",
+ value: "ball",
+ },
+ {
+ text: "Custom, complex simple shapes, convexHull",
+ value: "convexHull",
+ },
+ {
+ text: "Precision, TriMesh",
+ value: "trimesh",
+ },
+ ],
+ },
+ forces: {
+ acceptReporters: false,
+ items: [{
+ text: "Force",
+ value: "addForce",
+ },
+ {
+ text: "Torque (rotation)",
+ value: "addTorque",
+ },
+ {
+ text: "Apply Impulse",
+ value: "applyImpulse",
+ },
+ {
+ text: "Apply Torque Impulse (rotation)",
+ value: "applyTorqueImpulse",
+ },
+ {
+ text: "Linear Velocity",
+ value: "setLinvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "setAngvel",
+ },
+ ],
+ },
+ resetF: {
+ acceptReporters: false,
+ items: [{
+ text: "Forces",
+ value: "resetForces",
+ },
+ {
+ text: "Torques",
+ value: "resetTorques",
+ },
+ ],
+ },
+ },
+ };
+ }
+ joint(jointData, bodyA, bodyB) {
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
+ }
+
+ fixedJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ let RA = JSON.parse(args.RA).map(Number);
+ let RB = JSON.parse(args.RB).map(Number);
+
+ RA = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RA[0]),
+ THREE.MathUtils.degToRad(RA[1]),
+ THREE.MathUtils.degToRad(RA[2])
+ )
+ );
+ RB = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RB[0]),
+ THREE.MathUtils.degToRad(RB[1]),
+ THREE.MathUtils.degToRad(RB[2])
+ )
+ );
+
+ const data = RAPIER.JointData.fixed({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ },
+ RA, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ },
+ RB
+ );
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ sphericalJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+
+ const data = RAPIER.JointData.spherical({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ revoluteJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.revolute({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ prismaticJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.prismatic({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ createWorld(args) {
+ const v3 = JSON.parse(args.G).map(Number);
+ const gravity = {
+ x: v3[0],
+ y: v3[1],
+ z: v3[2],
+ };
+ physicsWorld = new RAPIER.World(gravity);
+
+ console.log(physicsWorld);
+ }
+
+ getWorld(args) {
+ if (args.PROPERTY === "log") {
+ console.log(physicsWorld);
+ return "logged";
+ }
+ return JSON.stringify(physicsWorld[args.PROPERTY]);
+ }
+
+ setRB(args) {
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.rigidBody[args.PROPERTY](value);
+ }
+ setC(args) {
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.collider[args.PROPERTY](value);
+ }
+
+ getRB(args) {
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.rigidBody[args.PROPERTY]());
+ }
+ getC(args) {
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.collider[args.PROPERTY]());
+ }
+
+ lockObjectAxis(args) {
+ let object = getObject(args.OBJECT);
+ const x = !JSON.parse(args.X);
+ const y = !JSON.parse(args.Y);
+ const z = !JSON.parse(args.Z);
+ object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
+ }
+
+ objectPhysics(args) {
+ let object = getObject(args.OBJECT);
+ object.physics = JSON.parse(args.state);
+
+ if (JSON.parse(args.state)) {
+ //if already exists delete:
+ if (object.rigidBody) {
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ /*asing a rigidbody and collider to object and add them to physicsWorld*/
+ let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
+ .setTranslation(object.position.x, object.position.y, object.position.z)
+ .setRotation({
+ w: object.quaternion._w,
+ x: object.quaternion._x,
+ y: object.quaternion._y,
+ z: object.quaternion._z,
+ });
+
+ let colliderDesc;
+ switch (args.collider) {
+ case "cuboid":
+ colliderDesc = createCuboidCollider(object);
+ break;
+ case "ball":
+ colliderDesc = createBallCollider(object);
+ break;
+ case "convexHull":
+ colliderDesc = createConvexHullCollider(object);
+ break;
+ case "trimesh":
+ colliderDesc = TriMesh(object);
+ break;
+ }
+ colliderDesc
+ .setSensor(JSON.parse(args.state2))
+ .setMass(args.mass)
+ .setDensity(args.density)
+ .setFriction(args.friction);
+
+ let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
+ let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
+
+ object.rigidBody = rigidBody;
+ object.collider = collider;
+ } else {
+ /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ }
+
+ enableCCD(args) {
+ let object = getObject(args.OBJECT);
+ if (object.physics) {
+ let rigidBody = object.rigidBody;
+ rigidBody.enableCcd(JSON.parse(args.state));
+ }
+ }
+
+ addForce(args) {
+ let object = getObject(args.OBJECT);
+ const vector = JSON.parse(args.VALUE).map(Number);
+
+ let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
+ if (args.SPACE === "local") {
+ force.applyQuaternion(object.quaternion);
+ }
+
+ object.rigidBody[args.PROPERTY](force, true);
+ }
+
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true);
+ }
+
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR);
+
+ let object = getObject(args.OBJECT);
+
+ let touching = false;
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ if (otherCollider === object.collider) touching = true;
+ });
+
+ return touching;
+ }
+
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR);
+
+ const touchedObjects = [];
+
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ // find owner of collider
+ const otherObject = scene.children.find((o) => o.collider === otherCollider);
+ console.log(otherCollider);
+ if (otherObject) touchedObjects.push(otherObject.name);
+ });
+
+ return JSON.stringify(touchedObjects);
+ }
+ }
+ Scratch.extensions.register(new RapierPhysics());
+
+ //Thanks to the PointerLock extension of Turbowarp
+ const mouse = vm.runtime.ioDevices.mouse;
+ let isLocked = false;
+ let isPointerLockEnabled = false;
+
+ let rect = threeRenderer.domElement.getBoundingClientRect();
+ document.addEventListener("resize", () => {
+ rect = threeRenderer.domElement.getBoundingClientRect();
+ });
+
+ const postMouseData = (e, isDown) => {
+ const {
+ movementX,
+ movementY
+ } = e;
+ const {
+ width,
+ height
+ } = rect;
+ const x = mouse._clientX + movementX;
+ const y = mouse._clientY - movementY;
+ mouse._clientX = x;
+ mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
+ mouse._clientY = y;
+ mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
+ if (typeof isDown === "boolean") {
+ const data = {
+ button: e.button,
+ isDown,
+ };
+ originalPostIOData(data);
+ }
+ };
+
+ const mouseDevice = vm.runtime.ioDevices.mouse;
+ const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
+ mouseDevice.postData = (data) => {
+ if (!isPointerLockEnabled) {
+ return originalPostIOData(data);
+ }
+ };
+
+ document.addEventListener(
+ "mousedown",
+ (e) => {
+ // @ts-expect-error
+ if (threeRenderer.domElement.contains(e.target)) {
+ if (isLocked) {
+ postMouseData(e, true);
+ } else if (isPointerLockEnabled) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mouseup",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e, false);
+ // @ts-expect-error
+ } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mousemove",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e);
+ }
+ },
+ true
+ );
+
+ document.addEventListener("pointerlockchange", () => {
+ isLocked = document.pointerLockElement === threeRenderer.domElement;
+ });
+ document.addEventListener("pointerlockerror", (e) => {
+ console.error("Pointer lock error", e);
+ });
+
+ const oldStep = vm.runtime._step;
+ vm.runtime._step = function(...args) {
+ const ret = oldStep.call(this, ...args);
+ if (isPointerLockEnabled) {
+ const {
+ width,
+ height
+ } = rect;
+ mouse._clientX = width / 2;
+ mouse._clientY = height / 2;
+ mouse._scratchX = 0;
+ mouse._scratchY = 0;
+ }
+ return ret;
+ };
+
+ vm.runtime.on("PROJECT_LOADED", () => {
+ isPointerLockEnabled = false;
+ if (isLocked) {
+ document.exitPointerLock();
+ }
+ });
+
+ class Pointerlock {
+ getInfo() {
+ return {
+ id: "threepointerlockmod",
+ name: "Pointerlock for Extra 3D",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setLocked",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set pointer lock [enabled]",
+ arguments: {
+ enabled: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ menu: "enabled",
+ },
+ },
+ },
+ {
+ opcode: "isLocked",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "pointer locked?",
+ },
+ ],
+ menus: {
+ enabled: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "true",
+ },
+ {
+ text: "disabled",
+ value: "false",
+ },
+ ],
+ },
+ },
+ };
+ }
+
+ setLocked(args) {
+ isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
+ if (!isPointerLockEnabled && isLocked) {
+ document.exitPointerLock();
+ }
+ }
+
+ isLocked() {
+ return isLocked;
+ }
+ }
+ Scratch.extensions.register(new Pointerlock());
+ });
+})(Scratch);
diff --git a/threejsD_BACKUP_13852.js b/threejsD_BACKUP_13852.js
new file mode 100644
index 0000000..d1db442
--- /dev/null
+++ b/threejsD_BACKUP_13852.js
@@ -0,0 +1,5029 @@
+/* jshint esversion: 11 */
+// Name: Extra 3D
+// ID: threejsExtension
+// Description: Use three js inside Turbowarp! A 3D graphics library.
+// By: Civero
+// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
+
+(function(Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Three-D extension must run unsandboxed");
+ }
+
+ if (Scratch.vm.runtime.isPackaged) {
+ alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
+ return;
+ }
+ //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
+
+ const vm = Scratch.vm;
+ const runtime = vm.runtime;
+ const renderer = Scratch.renderer;
+ const canvas = renderer.canvas;
+ const Cast = Scratch.Cast;
+ const menuIconURI =
+ "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
+
+ let alerts = false;
+ console.log("alerts are " + (alerts ? "enabled" : "disabled"));
+
+ let isMouseDown = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+ let prevMouse = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+
+ let lastWidth = 0;
+ let lastHeight = 0;
+
+ let THREE;
+ let clock;
+ let running;
+ let loopId;
+ //Addons
+ let GLTFLoader;
+ let gltf;
+ let OrbitControls;
+ let controls;
+ let BufferGeometryUtils;
+ let TextGeometry;
+ let fontLoad;
+ //Physics
+ let RAPIER;
+ let physicsWorld;
+
+ let threeRenderer;
+ let scene;
+ let camera;
+ let eulerOrder = "YXZ";
+
+ let composer;
+ let passes = {};
+ let customEffects = [];
+ let renderTargets = {};
+
+ let materials = {};
+ let geometries = {};
+ let lights = {};
+ let models = {};
+
+ let assets = {
+ //should i place materials, geometries; inside too?
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {}, //not the same as the global one! this one only stores textures
+ };
+
+ let raycastResult = [];
+
+ function resetor(level) {
+ camera = undefined;
+ composer.reset();
+
+ passes = {};
+ customEffects = [];
+ renderTargets = {};
+
+ materials = {};
+ geometries = {};
+ lights = {};
+ models = {};
+
+ if (level > 0) {
+ assets = {
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {},
+ };
+ }
+
+ updateComposers();
+ }
+
+ //utility
+ function vector3ToString(prop) {
+ if (!prop) return "0,0,0";
+
+ const x =
+ typeof prop.x === "number" ?
+ prop.x :
+ typeof prop._x === "number" ?
+ prop._x :
+ JSON.stringify(prop).includes("X") ?
+ prop :
+ 0;
+ const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
+ const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
+
+ return [x, y, z];
+ }
+
+ //objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true);
+ if (object) {
+ removeObject(name);
+ alerts ? alert(name + " already exsisted, will replace!") : null;
+ }
+ content.name = name;
+ content.rotation._order = eulerOrder;
+ parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+ content.physics = false;
+
+ object.add(content);
+ }
+
+ function removeObject(name) {
+ let object = getObject(name);
+ if (!object) return;
+
+ scene.remove(object);
+
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true);
+ physicsWorld.removeRigidBody(object.rigidBody, true);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ if (object.isLight) {
+ delete lights[name];
+ }
+ }
+
+ function getObject(name, isNew) {
+ let object = null;
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
+ return;
+ }
+ object = scene.getObjectByName(name);
+ if (!object && !isNew) {
+ alerts ? alert(name + " does not exist! Add it to scene") : null;
+ return;
+ }
+ return object;
+ }
+
+ //materials
+ function encodeCostume(name) {
+ if (name.startsWith("data:image/")) return name;
+ return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ }
+
+ function setTexutre(texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace;
+
+ if (mode === "Pixelate") {
+ texture.minFilter = THREE.NearestFilter;
+ texture.magFilter = THREE.NearestFilter;
+ } else {
+ //Blur
+ texture.minFilter = THREE.NearestMipmapLinearFilter;
+ texture.magFilter = THREE.NearestMipmapLinearFilter;
+ }
+
+ if (style === "Repeat") {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(x, y);
+ }
+
+ texture.generateMipmaps = true;
+ }
+ async function resizeImageToSquare(uri, size = 256) {
+ return new Promise((resolve) => {
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = size;
+ canvas.height = size;
+ const ctx = canvas.getContext("2d");
+
+ // clear + draw image scaled to fit canvas
+ ctx.clearRect(0, 0, size, size);
+ ctx.drawImage(img, 0, 0, size, size);
+
+ resolve(canvas.toDataURL()); // return normalized Data URI
+ //delete canvas?
+ };
+ img.src = uri;
+ });
+ }
+ //light
+ function updateShadowFrustum(light, focusPos) {
+ if (light.type !== "DirectionalLight") return;
+
+ // Frustum Size - Increase this value to cover a larger area.
+ const d = 50;
+
+ // Update Orthographic Shadow Camera Frustum
+ const shadowCamera = light.shadow.camera;
+
+ // Set the width/height of the frustum
+ shadowCamera.left = -d;
+ shadowCamera.right = d;
+ shadowCamera.top = d;
+ shadowCamera.bottom = -d;
+
+ // Determine ranges
+ shadowCamera.near = 0.1;
+ shadowCamera.far = 500;
+
+ // Position the Light and its Target
+ light.target.position.copy(focusPos);
+ const direction = light.position.clone().sub(light.target.position).normalize();
+ light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
+
+ // Ensure matrices are updated.
+ light.target.updateMatrixWorld();
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
+ //composer
+ function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
+
+ // always recreate the RenderPass to point to the current scene/camera
+ passes["Render"] = new RenderPass(scene, camera);
+
+ // ensure composer has a RenderPass as the first pass
+ const hasRender = composer.passes.some((p) => p && p.scene);
+ if (!hasRender) composer.addPass(passes["Render"]);
+ else {
+ // if composer already has one, replace it so it references current scene/camera
+ const idx = composer.passes.findIndex((p) => p && p.scene);
+ composer.passes[idx] = passes["Render"];
+ }
+ }
+ //utility
+ function getMouseNDC(event) {
+ // Use threeRenderer.domElement for correct offset
+ const rect = threeRenderer.domElement.getBoundingClientRect();
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ return [x, y];
+ }
+
+ function checkCanvasSize() {
+ const {
+ width,
+ height
+ } = canvas;
+ if (width !== lastWidth || height !== lastHeight) {
+ lastWidth = width;
+ lastHeight = height;
+ resize();
+ }
+ requestAnimationFrame(checkCanvasSize); //rerun next frame
+ }
+ //physics
+ function computeWorldBoundingBox(mesh) {
+ // Create a Box3 in world coordinates
+ const box = new THREE.Box3().setFromObject(mesh);
+ const size = new THREE.Vector3();
+ box.getSize(size);
+ const center = new THREE.Vector3();
+ box.getCenter(center);
+ return {
+ size,
+ center,
+ };
+ }
+
+ function createCuboidCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
+ return collider;
+ }
+
+ function createBallCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ // radius = 1/2 of the largest verticie
+ const radius = Math.max(size.x, size.y, size.z) / 2;
+ const collider = RAPIER.ColliderDesc.ball(radius);
+ return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
+ }
+
+ function createConvexHullCollider(mesh) {
+ mesh.updateWorldMatrix(true, false);
+
+ const position = mesh.geometry.attributes.position;
+ const vertices = [];
+ const vertex = new THREE.Vector3();
+
+ // Matrix for scale only
+ const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
+
+ for (let i = 0; i < position.count; i++) {
+ vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
+ vertices.push(vertex.x, vertex.y, vertex.z);
+ }
+
+ const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
+ return collider;
+ }
+
+ function TriMesh(mesh) {
+ // Get the positions array (from your geoPoints function)
+ const positions = mesh.geometry.attributes.position.array;
+ const numVertices = positions.length / 3;
+
+ // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
+ const indices = Array.from({
+ length: numVertices,
+ },
+ (_, i) => i
+ );
+
+ const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
+
+ return collider;
+ }
+
+ function getModel(model, name) {
+ const file = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === model);
+ if (!file) return;
+
+ return new Promise((resolve, reject) => {
+ gltf.parse(
+ file.asset.data.buffer,
+ "",
+ (gltf) => {
+ const root = gltf.scene;
+ root.traverse((child) => {
+ if (child.isMesh) {
+ child.castShadow = true;
+ child.receiveShadow = true;
+ }
+ });
+
+ const mixer = new THREE.AnimationMixer(root);
+ const actions = {};
+ gltf.animations.forEach((clip) => {
+ const act = mixer.clipAction(clip);
+ act.clampWhenFinished = true;
+ actions[clip.name] = act;
+ });
+
+ models[name] = {
+ root,
+ mixer,
+ actions,
+ };
+ resolve(root);
+ },
+ (error) => {
+ console.error("Error parsing GLB model:", error);
+ reject(error);
+ }
+ );
+ });
+ }
+ async function openFileExplorer(format) {
+ return new Promise((resolve) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = format;
+ input.multiple = false;
+ input.onchange = () => {
+ resolve(input.files);
+ input.remove();
+ };
+ input.click();
+ });
+ }
+
+ function getMeshesUsingTexture(scene, targetTexture) {
+ const meshes = [];
+
+ scene.traverse((object) => {
+ if (object.material) {
+ const materials = Array.isArray(object.material) ? object.material : [object.material];
+ for (const material of materials) {
+ if (material.map === targetTexture) {
+ meshes.push(object);
+ break;
+ }
+ }
+ }
+ });
+
+ return meshes;
+ }
+
+ function getAsset(path) {
+ if (typeof path == "string") {
+ //string?
+ if (path.includes("/")) {
+ //has the /?
+ const value = path.split("/");
+ console.log(value[0], value[1]);
+ return assets[value[0]][value[1]];
+ }
+ }
+
+ return JSON.parse(path); //boolean or number
+ }
+
+ let mouseNDC = [0, 0];
+ //loops/init
+ function stopLoop() {
+ if (!running) return;
+ running = false;
+
+ if (loopId) {
+ cancelAnimationFrame(loopId);
+ loopId = null;
+ if (threeRenderer) threeRenderer.clear();
+ }
+ }
+ async function load() {
+ if (!THREE) {
+ // @ts-ignore
+<<<<<<< HEAD
+ THREE = await import("https://esm.sh/three@0.180.0")
+ window._THREE_ = THREE
+=======
+ THREE = await import("https://esm.sh/three@0.180.0");
+>>>>>>> e4a038b (Update threejsD.js)
+ //Addons
+ GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
+ OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
+ BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
+ TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
+ const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
+ fontLoad = new FontLoader.FontLoader();
+
+ const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
+ const {
+ EffectComposer,
+ EffectPass,
+ RenderPass,
+
+ Effect,
+ BloomEffect,
+ GodRaysEffect,
+ DotScreenEffect,
+ DepthOfFieldEffect,
+
+ BlendFunction,
+ } = POSTPROCESSING;
+ //so i can use them later as global
+ window.EffectComposer = EffectComposer;
+ window.EffectPass = EffectPass;
+ window.RenderPass = RenderPass;
+ window.Effect = Effect;
+ window.BloomEffect = BloomEffect;
+ window.GodRaysEffect = GodRaysEffect;
+ window.DotScreenEffect = DotScreenEffect;
+ window.DepthOfFieldEffect = DepthOfFieldEffect;
+ window.BlendFunction = BlendFunction;
+
+ RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
+ await RAPIER.init();
+
+ threeRenderer = new THREE.WebGLRenderer({
+ powerPreference: "high-performance",
+ antialias: false,
+ stencil: false,
+ depth: true,
+ });
+ threeRenderer.setPixelRatio(window.devicePixelRatio);
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
+ //threeRenderer.toneMappingExposure = 1.0 //(test)
+
+ threeRenderer.shadowMap.enabled = true;
+ threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
+ threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
+
+ gltf = new GLTFLoader.GLTFLoader();
+ clock = new THREE.Clock();
+
+ // Example: create a composer
+ composer = new EffectComposer(threeRenderer, {
+ frameBufferType: THREE.HalfFloatType,
+ });
+
+ renderer.addOverlay(threeRenderer.domElement, "manual");
+ renderer.addOverlay(canvas, "manual");
+ renderer.setBackgroundColor(1, 1, 1, 0);
+
+ resize();
+
+ window.addEventListener("mousedown", (e) => {
+ if (e.button === 0) isMouseDown.left = true;
+ if (e.button === 1) isMouseDown.middle = true;
+ if (e.button === 2) isMouseDown.right = true;
+ });
+ window.addEventListener("mouseup", (e) => {
+ if (e.button === 0) isMouseDown.left = false;
+ prevMouse.left = false;
+ if (e.button === 1) isMouseDown.middle = false;
+ prevMouse.middle = false;
+ if (e.button === 2) isMouseDown.right = false;
+ prevMouse.right = false;
+ });
+ // prevent contextmenu on right click
+ threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
+
+ threeRenderer.domElement.addEventListener("mousemove", (event) => {
+ mouseNDC = getMouseNDC(event);
+ });
+
+ running = false;
+ load();
+
+<<<<<<< HEAD
+ startRenderLoop()
+ runtime.on('PROJECT_START', () => startRenderLoop())
+ runtime.on('PROJECT_STOP_ALL', () => stopLoop())
+ runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
+ checkCanvasSize()
+=======
+ startRenderLoop();
+ runtime.on("PROJECT_START", () => startRenderLoop());
+ runtime.on("PROJECT_STOP_ALL", () => stopLoop());
+ runtime.on("STAGE_SIZE_CHANGED", () => {
+ requestAnimationFrame(() => resize());
+ });
+ //if (!runtime.isPackaged) checkCanvasSize() //only in editor
+>>>>>>> e4a038b (Update threejsD.js)
+ }
+ }
+
+ function startRenderLoop() {
+ if (running) return;
+ running = true;
+
+ const loop = () => {
+ if (!running) return;
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step();
+
+ scene.children.forEach((obj) => {
+ if (!obj.isMesh || !obj.physics) return;
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation());
+ obj.quaternion.copy(obj.rigidBody.rotation());
+ }
+ });
+ }
+ if (scene && camera) {
+ if (controls) controls.update();
+
+ const delta = clock.getDelta();
+ Object.values(models).forEach((model) => {
+ if (model) model.mixer.update(delta);
+ });
+
+ Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
+
+ //update custom effects time
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("time")) {
+ e.uniforms.get("time").value += delta;
+ }
+ });
+ Object.values(renderTargets).forEach((t) => {
+ if (t.camera.type == "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height;
+ t.camera.updateProjectionMatrix();
+ }
+ // get meshes using the texture associated with this target
+ const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = false;
+ });
+
+ if (t.camera.type == "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target);
+ threeRenderer.clear(true, true, true);
+ threeRenderer.render(scene, t.camera);
+ } else {
+ t.target.clear(threeRenderer);
+ t.camera.update(threeRenderer, scene); //cubeCamera
+ }
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = true;
+ });
+ });
+
+ camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
+ camera.updateProjectionMatrix();
+ threeRenderer.setRenderTarget(null);
+ composer.render(delta);
+ }
+
+ loopId = requestAnimationFrame(loop);
+ };
+
+ loopId = requestAnimationFrame(loop);
+ }
+
+ function resize() {
+ const w = canvas.width;
+ const h = canvas.height;
+
+ threeRenderer.setSize(w, h);
+ composer.setSize(w, h);
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("resolution")) {
+ e.uniforms.get("resolution").value.set(w, h);
+ }
+ });
+
+ if (camera) {
+ camera.aspect = w / h;
+ camera.updateProjectionMatrix();
+ }
+ }
+ //wait until all packages are loaded
+ Promise.resolve(load()).then(() => {
+ class threejsExtension {
+ getInfo() {
+ return {
+ id: "threejsExtension",
+ name: "Extra 3D",
+ color1: "#222222",
+ color2: "#222222",
+ color3: "#11cc99",
+ menuIconURI,
+ blockIconURI: menuIconURI,
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Show Docs",
+ func: "openDocs",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Toggle Alerts",
+ func: "alerts",
+ },
+ ],
+ menus: {},
+ };
+ }
+ openDocs() {
+ open("https://civ3ro.github.io/extensions/Documentation/");
+ }
+ alerts() {
+ alerts = !alerts;
+ alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
+ }
+ }
+ Scratch.extensions.register(new threejsExtension());
+
+ class ThreeRenderer {
+ getInfo() {
+ return {
+ id: "threeRenderer",
+ name: "Three Renderer",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setRendererRatio",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Pixel Ratio to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "eulerOrder",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set euler order to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "YXZ",
+ },
+ },
+ },
+ ],
+ menus: {},
+ };
+ }
+
+ setRendererRatio(args) {
+ threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
+ }
+ eulerOrder(args) {
+ eulerOrder = args.VALUE;
+ console.log("euler order set to", eulerOrder);
+ }
+ }
+ Scratch.extensions.register(new ThreeRenderer());
+
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
+ getInfo() {
+ return {
+ id: "threeScene",
+ name: "Three Scene",
+ color1: "#4638c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newScene",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new Scene [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ },
+ },
+
+ {
+ opcode: "setSceneProperty",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Scene [PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneProperties",
+ defaultValue: "background",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "getSceneObjects",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get Scene [THING]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneThings",
+ },
+ },
+ },
+ {
+ opcode: "reset",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Reset Everything",
+ },
+ ],
+ menus: {
+ sceneProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Background",
+ value: "background",
+ },
+ {
+ text: "Background Blurriness",
+ value: "backgroundBlurriness",
+ },
+ {
+ text: "Background Intensity",
+ value: "backgroundIntensity",
+ },
+ {
+ text: "Background Rotation",
+ value: "backgroundRotation",
+ },
+ {
+ text: "Environment",
+ value: "environment",
+ },
+ {
+ text: "Environment Intensity",
+ value: "environmentIntensity",
+ },
+ {
+ text: "Environment Rotation",
+ value: "environmentRotation",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ ],
+ },
+ sceneThings: {
+ acceptReporters: false,
+ items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
+ },
+ },
+ };
+ }
+
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME;
+ scene.background = new THREE.Color("#222");
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {
+ ...this.scenes,
+ ...scene,
+ };
+ resetor(0);
+ }
+
+ reset() {
+ resetor(1);
+ }
+
+ async setSceneProperty(args) {
+ const property = args.PROPERTY;
+ const value = getAsset(args.VALUE);
+
+ scene[property] = value;
+ }
+ getSceneObjects(args) {
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse((obj) => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ else if (args.THING === "Scene Properties") {
+ console.log(scene);
+ return "check console";
+ } else if (args.THING === "Other assets") return JSON.stringify(assets);
+
+ return JSON.stringify(names); // if objects
+ }
+ }
+ Scratch.extensions.register(new ThreeScene());
+
+ class ThreeCameras {
+ getInfo() {
+ return {
+ id: "threeCameras",
+ name: "Three Cameras",
+ color1: "#38c59bff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add camera [TYPE] [CAMERA] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraTypes",
+ },
+ },
+ },
+ {
+ opcode: "setCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "0.1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "getCamera",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get camera [PROPERTY] of [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "renderSceneCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rendering camera to [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "cubeCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "cubeCamera",
+ },
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "renderTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set a RenderTarget: [RT] for camera [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "sizeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set RenderTarget [RT] size to [W] [H]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ W: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 480,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 360,
+ },
+ },
+ },
+ {
+ opcode: "getTarget",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get RenderTarget: [RT] texture",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "removeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove RenderTarget: [RT]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ ],
+ menus: {
+ cameraTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Perspective",
+ value: "PerspectiveCamera",
+ }, ],
+ },
+ cameraProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Near",
+ value: "near",
+ },
+ {
+ text: "Far",
+ value: "far",
+ },
+ {
+ text: "FOV",
+ value: "fov",
+ },
+ {
+ text: "Focus (nothing...)",
+ value: "focus",
+ },
+ {
+ text: "Zoom",
+ value: "zoom",
+ },
+ ],
+ },
+ },
+ };
+ }
+ addCamera(args) {
+ let v2 = new THREE.Vector2();
+ threeRenderer.getSize(v2);
+ const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
+ object.position.z = 3;
+
+ createObject(args.CAMERA, object, args.GROUP);
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA);
+ object[args.PROPERTY] = args.VALUE;
+ object.updateProjectionMatrix();
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA);
+ const value = JSON.stringify(object[args.PROPERTY]);
+ return value;
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA);
+ if (!object) return;
+ camera = object;
+ //reset composer, else it does not update.
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
+
+ cubeCamera(args) {
+ // Create cube render target
+ const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
+ generateMipmaps: true,
+ });
+ // Create cube camera
+ const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
+ createObject(args.CAMERA, cubeCamera, args.GROUP);
+
+ renderTargets[args.RT] = {
+ target: cubeRenderTarget,
+ camera: cubeCamera,
+ };
+ assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
+ }
+
+ renderTarget(args) {
+ let object = getObject(args.CAMERA);
+ const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
+ generateMipmaps: false,
+ });
+
+ renderTargets[args.RT] = {
+ target: renderTarget,
+ camera: object,
+ };
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H);
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture;
+ console.log(t, renderTargets[args.RT]);
+ return `renderTargets/${t.uuid}`;
+ }
+ removeTarget(args) {
+ delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
+ renderTargets[args.RT].target.dispose();
+ delete renderTargets[args.RT];
+ }
+ }
+ Scratch.extensions.register(new ThreeCameras());
+
+ class ThreeObjects {
+ getInfo() {
+ return {
+ id: "threeObjects",
+ name: "Three Objects",
+ color1: "#38c567ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add object [OBJECT3D] [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "cloneObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myClone",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "setObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "getObject",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of object [OBJECT3D]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ },
+ },
+ {
+ opcode: "objectE",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there an object [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "removeObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove object [OBJECT3D] from scene",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: " ↳ Transforms",
+ },
+ {
+ opcode: "setObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
+ //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {
+ opcode: "getObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of [OBJECT3D]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Materials",
+ },
+ {
+ opcode: "newMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new material [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialTypes",
+ defaultValue: "MeshStandardMaterial",
+ },
+ },
+ },
+ {
+ opcode: "materialE",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a material [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "removeMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove material [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "setMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [PROPERTY] of [NAME] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialProperties",
+ defaultValue: "color",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "setBlending",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] blending to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ },
+ },
+ },
+ {
+ opcode: "setDepth",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] depth to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "depthModes",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Geometries",
+ },
+ {
+ opcode: "newGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new geometry [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "geometryTypes",
+ defaultValue: "BoxGeometry",
+ },
+ },
+ },
+ {
+ opcode: "geometryE",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a geometry [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "removeGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "newGeo",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new empty geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoPoints",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] vertex points to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoUVs",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] UVs to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[UVs]",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "splines",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create spline [NAME] from curve [CURVE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "splineModel",
+ extensions: ["colours_operators"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ MODEL: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ SPACING: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Convert font to JSON",
+ func: "openConv",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load JSON font file",
+ func: "loadFont",
+ },
+ {
+ opcode: "text",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myText",
+ },
+ TEXT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "C-369",
+ },
+ FONT: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "fonts",
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ D: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ CS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 6,
+ },
+ },
+ },
+ ],
+ menus: {
+ objectVector3: {
+ acceptReporters: false,
+ items: [{
+ text: "Positon",
+ value: "position",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Facing Direction (.up)",
+ value: "up",
+ },
+ ],
+ },
+ objectProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Geometry",
+ value: "geometry",
+ },
+ {
+ text: "Material",
+ value: "material",
+ },
+ {
+ text: "Visible (true/false)",
+ value: "visible",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh",
+ value: "Mesh",
+ },
+ {
+ text: "Sprite",
+ value: "Sprite",
+ },
+ {
+ text: "Points",
+ value: "Points",
+ },
+ {
+ text: "Line",
+ value: "Line",
+ },
+ {
+ text: "Group",
+ value: "Group",
+ },
+ ],
+ },
+ XYZ: {
+ acceptReporters: false,
+ items: [{
+ text: "X",
+ value: "x",
+ },
+ {
+ text: "Y",
+ value: "y",
+ },
+ {
+ text: "Z",
+ value: "z",
+ },
+ ],
+ },
+ materialProperties: {
+ acceptReporters: false,
+ items: [
+ "|GENERAL| <-- not a property",
+ {
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map",
+ value: "map",
+ },
+ {
+ text: "Opacity",
+ value: "opacity",
+ },
+ {
+ text: "Transparent",
+ value: "transparent",
+ },
+ {
+ text: "Alpha Map",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test",
+ value: "alphaTest",
+ },
+ {
+ text: "Depth Test",
+ value: "depthTest",
+ },
+ {
+ text: "Depth Write",
+ value: "depthWrite",
+ },
+ {
+ text: "Color Write",
+ value: "colorWrite",
+ },
+ {
+ text: "Side",
+ value: "side",
+ },
+ {
+ text: "Visible",
+ value: "visible",
+ },
+ /*
+ { text: "Blending", value: "blending" },
+ { text: "Blend Src", value: "blendSrc" },
+ { text: "Blend Dst", value: "blendDst" },
+ { text: "Blend Equation", value: "blendEquation" },
+ { text: "Blend Src Alpha", value: "blendSrcAlpha" },
+ { text: "Blend Dst Alpha", value: "blendDstAlpha" },
+ { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
+ {
+ text: "Blend Aplha",
+ value: "blendAplha",
+ },
+ {
+ text: "Blend Color",
+ value: "blendColor",
+ },
+ {
+ text: "Alpha Hash",
+ value: "alphaHash",
+ },
+ {
+ text: "Premultiplied Alpha",
+ value: "premultipliedAlpha",
+ },
+
+ {
+ text: "Tone Mapped",
+ value: "toneMapped",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ {
+ text: "Flat Shading",
+ value: "flatShading",
+ },
+
+ "|MESH Standard / Physical| <-- not a property",
+ {
+ text: "Metalness",
+ value: "metalness",
+ },
+ {
+ text: "Metalness Map",
+ value: "metalnessMap",
+ },
+ {
+ text: "Roughness",
+ value: "roughness",
+ },
+ {
+ text: "Reflectivity",
+ value: "reflectivity",
+ },
+ {
+ text: "Roughness Map",
+ value: "roughnessMap",
+ },
+ {
+ text: "Emissive",
+ value: "emissive",
+ },
+ {
+ text: "Emissive Intensity",
+ value: "emissiveIntensity",
+ },
+ {
+ text: "Emissive Map",
+ value: "emissiveMap",
+ },
+ {
+ text: "Env Map",
+ value: "envMap",
+ },
+ {
+ text: "Env Map Intensity",
+ value: "envMapIntensity",
+ },
+ {
+ text: "Env Map Rotation",
+ value: "envMapRotation",
+ },
+ {
+ text: "Ior",
+ value: "ior",
+ },
+ {
+ text: "Refraction Ratio",
+ value: "refractionRatio",
+ },
+ {
+ text: "Clearcoat",
+ value: "clearcoat",
+ },
+ {
+ text: "Clearcoat Map",
+ value: "clearcoatMap",
+ },
+ {
+ text: "Clearcoat Roughness",
+ value: "clearcoatRoughness",
+ },
+ {
+ text: "Clearcoat Roughness Map",
+ value: "clearcoatRoughnessMap",
+ },
+ {
+ text: "Dispersion",
+ value: "dispersion",
+ },
+ {
+ text: "Sheen",
+ value: "sheen",
+ },
+ {
+ text: "Sheen Color",
+ value: "sheenColor",
+ },
+ {
+ text: "Sheen Color Map",
+ value: "sheenColorMap",
+ },
+ {
+ text: "Sheen Roughness",
+ value: "sheenRoughness",
+ },
+ {
+ text: "Sheen Roughness Map",
+ value: "sheenRoughnessMap",
+ },
+ {
+ text: "Specular Color",
+ value: "specularColor",
+ },
+ {
+ text: "Specular Color Map",
+ value: "specularColorMap",
+ },
+ {
+ text: "Specular Intensity",
+ value: "specularIntensity",
+ },
+ {
+ text: "Specular Intensity Map",
+ value: "specularIntensityMap",
+ },
+ {
+ text: "Transmission",
+ value: "transmission",
+ },
+ {
+ text: "Transmission Map",
+ value: "transmissionMap",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Thickness Map",
+ value: "thicknessMap",
+ },
+ {
+ text: "Anisotropy",
+ value: "anisotropy",
+ },
+ {
+ text: "Anisotropy Map",
+ value: "anisotropyMap",
+ },
+ {
+ text: "Anisotropy Rotation",
+ value: "anisotropyRotation",
+ },
+ {
+ text: "Attenuation Distance",
+ value: "attenuationDistance",
+ },
+ {
+ text: "Attenuation Color",
+ value: "attenuationColor",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Iridescence",
+ value: "iridescence",
+ },
+ {
+ text: "Iridescence Ior",
+ value: "iridescenceIOR",
+ },
+ {
+ text: "Iridescence Map",
+ value: "iridescenceMap",
+ },
+ {
+ text: "Iridescence Thickness Range",
+ value: "iridescenceThicknessRange",
+ },
+
+ "|MESH Displacement / Normal / Bump| <-- not a property",
+ {
+ text: "Displacement Map",
+ value: "displacementMap",
+ },
+ {
+ text: "Displacement Scale",
+ value: "displacementScale",
+ },
+ {
+ text: "Displacement Bias",
+ value: "displacementBias",
+ },
+ {
+ text: "Bump Map",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ {
+ text: "Normal Map Type",
+ value: "normalMapType",
+ },
+
+ "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
+ {
+ text: "Shininess",
+ value: "shininess",
+ },
+
+ {
+ text: "Wireframe",
+ value: "wireframe",
+ },
+ {
+ text: "Wireframe Linewidth",
+ value: "wireframeLinewidth",
+ },
+ {
+ text: "Wireframe Linecap",
+ value: "wireframeLinecap",
+ },
+ {
+ text: "Wireframe Linejoin",
+ value: "wireframeLinejoin",
+ },
+
+ "|POINTS| <-- not a property",
+ {
+ text: "Size",
+ value: "size",
+ },
+ {
+ text: "Size Attenuation",
+ value: "sizeAttenuation",
+ },
+
+ "|LINES| <-- not a property",
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Dash Size",
+ value: "dashSize",
+ },
+ {
+ text: "Gap Size",
+ value: "gapSize",
+ },
+
+ "|SPRITES| <-- not a property",
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [{
+ text: "No Blending",
+ value: "NoBlending",
+ },
+ {
+ text: "Normal Blending",
+ value: "NormalBlending",
+ },
+ {
+ text: "Additive Blending",
+ value: "AdditiveBlending",
+ },
+ {
+ text: "Subtractive Blending",
+ value: "SubtractiveBlending",
+ },
+ {
+ text: "Multiply Blending",
+ value: "MultiplyBlending",
+ },
+ {
+ text: "Custom Blending",
+ value: "CustomBlending",
+ },
+ ],
+ },
+ depthModes: {
+ acceptReporters: false,
+ items: [{
+ text: "Never Depth",
+ value: "NeverDepth",
+ },
+ {
+ text: "Always Depth",
+ value: "AlwaysDepth",
+ },
+ {
+ text: "Equal Depth",
+ value: "EqualDepth",
+ },
+ {
+ text: "Less Depth",
+ value: "LessDepth",
+ },
+ {
+ text: "Less Equal Depth",
+ value: "LessEqualDepth",
+ },
+ {
+ text: "Greater Equal Depth",
+ value: "GreaterEqualDepth",
+ },
+ {
+ text: "Greater Depth",
+ value: "GreaterDepth",
+ },
+ {
+ text: "Not Equal Depth",
+ value: "NotEqualDepth",
+ },
+ ],
+ },
+ materialTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh Basic Material",
+ value: "MeshBasicMaterial",
+ },
+ {
+ text: "Mesh Standard Material",
+ value: "MeshStandardMaterial",
+ },
+ {
+ text: "Mesh Physical Material",
+ value: "MeshPhysicalMaterial",
+ },
+ {
+ text: "Mesh Lambert Material",
+ value: "MeshLambertMaterial",
+ },
+ {
+ text: "Mesh Phong Material",
+ value: "MeshPhongMaterial",
+ },
+ {
+ text: "Mesh Depth Material",
+ value: "MeshDepthMaterial",
+ },
+ {
+ text: "Mesh Normal Material",
+ value: "MeshNormalMaterial",
+ },
+ {
+ text: "Mesh Matcap Material",
+ value: "MeshMatcapMaterial",
+ },
+ {
+ text: "Mesh Toon Material",
+ value: "MeshToonMaterial",
+ },
+ {
+ text: "Line Basic Material",
+ value: "LineBasicMaterial",
+ },
+ {
+ text: "Line Dashed Material",
+ value: "LineDashedMaterial",
+ },
+ {
+ text: "Points Material",
+ value: "PointsMaterial",
+ },
+ {
+ text: "Sprite Material",
+ value: "SpriteMaterial",
+ },
+ {
+ text: "Shadow Material",
+ value: "ShadowMaterial",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ geometryTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box Geometry",
+ value: "BoxGeometry",
+ },
+ {
+ text: "Sphere Geometry",
+ value: "SphereGeometry",
+ },
+ {
+ text: "Cylinder Geometry",
+ value: "CylinderGeometry",
+ },
+ {
+ text: "Plane Geometry",
+ value: "PlaneGeometry",
+ },
+ {
+ text: "Circle Geometry",
+ value: "CircleGeometry",
+ },
+ {
+ text: "Torus Geometry",
+ value: "TorusGeometry",
+ },
+ {
+ text: "Torus Knot Geometry",
+ value: "TorusKnotGeometry",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model! (GLB Loader category)"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ fonts: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".json"));
+ if (models.length < 1) return [
+ ["Load a font!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ addObject(args) {
+ const object = new THREE[args.TYPE]();
+
+ object.castShadow = true;
+ object.receiveShadow = true;
+
+ createObject(args.OBJECT3D, object, args.GROUP);
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D);
+ const clone = object.clone(true);
+ clone.name;
+ createObject(args.NAME, clone, args.GROUP);
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ let values = JSON.parse(args.VALUE);
+
+ function degToRad(deg) {
+ return (deg * Math.PI) / 180;
+ }
+
+ if (object.rigidBody) {
+ const x = values[0];
+ const y = values[1];
+ const z = values[2];
+ if (args.PROPERTY === "rotation") {
+ const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
+ const quaternion = new THREE.Quaternion();
+ quaternion.setFromEuler(euler);
+
+ object.rigidBody.setRotation({
+ x: quaternion.x,
+ y: quaternion.y,
+ z: quaternion.z,
+ w: quaternion.w,
+ });
+ } else if (args.PROPERTY === "position") {
+ object.rigidBody.setTranslation({
+ x: x,
+ y: y,
+ z: z,
+ },
+ true
+ );
+ }
+ return;
+ }
+
+ if (object.isCamera == true && controls) {}
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map((v) => (v * Math.PI) / 180);
+ object.rotation.set(0, 0, 0);
+ }
+ if (object.isDirectionalLight == true) {
+ object.pos = new THREE.Vector3(...values);
+ console.log(true, values, object.pos);
+ return;
+ }
+ object[args.PROPERTY].set(...values);
+
+ if (object.type == "CubeCamera") object.updateCoordinateSystem();
+ }
+ /*
+ changeObjectV3(args) {
+ getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.x += values[0]
+ object.rotation.y += values[1]
+ object.rotation.z += values[2]
+ }
+ else {
+ object[args.PROPERTY].add(...values);
+ }
+ }
+ changeObjectXV3(args) {
+ getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "rotation") value = value * Math.PI / 180
+
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let values = vector3ToString(object[args.PROPERTY]);
+ if (args.PROPERTY === "rotation") {
+ const toDeg = Math.PI / 180;
+ values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
+ }
+
+ return JSON.stringify(values);
+ }
+ setObject(args) {
+ let object = getObject(args.OBJECT3D);
+ let value = args.VALUE;
+ if (args.PROPERTY === "material") {
+ const mat = materials[args.NAME];
+ if (mat) value = mat;
+ else value = undefined;
+ } else if (args.PROPERTY === "geometry") {
+ const geo = geometries[args.NAME];
+ if (geo) value = geo;
+ else value = undefined;
+ } else value = !!value;
+
+ if (value == undefined) return; //invalid geo/mat
+ object[args.PROPERTY] = value;
+ }
+ getObject(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let value;
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value;
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D);
+ }
+ objectE(args) {
+ return scene.children.map((o) => o.name).includes(args.NAME);
+ }
+
+ //defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
+
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
+ const mat = materials[args.NAME];
+
+ let value = args.VALUE;
+
+ if (args.VALUE == "false") value = false;
+
+ if (args.PROPERTY == "side") {
+ value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
+ } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
+ else value = getAsset(value);
+
+ console.log("o:", args.VALUE, typeof args.VALUE);
+ console.log("r:", value, typeof value);
+
+ mat[args.PROPERTY] = await value; //await incase its a texture
+ mat.needsUpdate = true;
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME];
+ mat.blending = THREE[args.VALUE];
+ mat.premultipliedAlpha = true;
+ mat.needsUpdate = true;
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME];
+ mat.depthFunc = THREE[args.VALUE];
+ mat.needsUpdate = true;
+ }
+ removeMaterial(args) {
+ const mat = materials[args.NAME];
+ mat.dispose();
+ delete materials[args.NAME];
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false;
+ }
+
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ const geo = new THREE[args.TYPE]();
+ geo.name = args.NAME;
+
+ geometries[args.NAME] = geo;
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo[args.PROPERTY] = args.VALUE;
+
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo.dispose();
+ delete geometries[args.NAME];
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false;
+ }
+
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry();
+ geometry.name = args.NAME;
+ geometries[args.NAME] = geometry;
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME];
+ const positions = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v3 of each vertex of each triangle
+
+ geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
+ geometry.computeVertexNormals();
+
+ geometry.needsUpdate = true;
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME];
+ const UVs = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v2 of each UV of each triangle
+
+ geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
+ geometry.needsUpdate = true;
+ }
+
+ splines(args) {
+ const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
+ geometry.name = args.NAME;
+
+ geometries[args.NAME] = geometry;
+ }
+
+ async splineModel(args) {
+ const model = await getModel(args.MODEL, args.NAME);
+ if (!model) return console.warn("Model not found:", args.MODEL);
+
+ const curve = getAsset(args.CURVE);
+ const spacing = parseFloat(args.SPACING) || 1;
+ const curveLength = curve.getLength();
+ const divisions = Math.floor(curveLength / spacing);
+
+ const geomList = [];
+ const matList = [];
+
+ for (let i = 0; i <= divisions; i++) {
+ const t = i / divisions;
+ const pos = curve.getPointAt(t);
+ const tangent = curve.getTangentAt(t);
+
+ const temp = model.clone(true);
+ temp.position.copy(pos);
+
+ const up = new THREE.Vector3(0, 0, 1);
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
+ temp.quaternion.copy(quat);
+
+ temp.updateMatrixWorld(true);
+
+ temp.traverse((child) => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone();
+ geom.applyMatrix4(child.matrixWorld);
+ geomList.push(geom);
+ matList.push(child.material); //.clone() ?
+ }
+ });
+ }
+
+ const validGeoms = geomList.filter((g) => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
+ if (!ok) console.warn("geometry skipped:", g);
+ return ok;
+ });
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
+ merged.computeBoundingBox();
+ merged.computeBoundingSphere();
+
+ merged.name = args.NAME;
+ geometries[args.NAME] = merged;
+ matList.name = args.NAME;
+ materials[args.NAME] = matList;
+ }
+
+ async text(args) {
+ const fontFile = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === args.FONT);
+ if (!fontFile) return;
+
+ const json = new TextDecoder().decode(fontFile.asset.data.buffer);
+ const fontData = JSON.parse(json);
+
+ const font = fontLoad.parse(fontData);
+
+ const params = {
+ font: font,
+ size: JSON.parse(args.S),
+ height: JSON.parse(args.D),
+ curveSegments: JSON.parse(args.CS),
+ bevelEnabled: false,
+ };
+ const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
+ geometry.computeVertexNormals();
+ geometry.center(); // optional, recenters the text
+
+ geometry.name = args.NAME;
+
+ geometries[args.NAME] = geometry;
+ }
+
+ async loadFont() {
+ openFileExplorer(".json").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ // From lily's assets
+ // // Thank you PenguinMod for providing this code.
+
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Font loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading font.");
+ }
+
+ // End of PenguinMod
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ openConv() {
+ {
+ open("https://gero3.github.io/facetype.js/");
+ }
+ }
+ }
+ Scratch.extensions.register(new ThreeObjects());
+
+ class ThreeLights {
+ getInfo() {
+ return {
+ id: "threeLights",
+ name: "Three Lights",
+ color1: "#c7a22aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add light [NAME] type [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightTypes",
+ },
+ },
+ },
+ {
+ opcode: "setLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set light [NAME][PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightProperties",
+ defaultValue: "intensity",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ lightTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Ambient Light",
+ value: "AmbientLight",
+ },
+ {
+ text: "Directional Light",
+ value: "DirectionalLight",
+ },
+ {
+ text: "Point Light",
+ value: "PointLight",
+ },
+ {
+ text: "Hemisphere Light",
+ value: "HemisphereLight",
+ },
+ {
+ text: "Spot Light",
+ value: "SpotLight",
+ },
+ ],
+ },
+ lightProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Intensity",
+ value: "intensity",
+ },
+ {
+ text: "Cast Shadow?",
+ value: "castShadow",
+ },
+ {
+ text: "Ground Color (HemisphereLight)",
+ value: "groundColor",
+ },
+ {
+ text: "Map (SpotLight)",
+ value: "map",
+ },
+ {
+ text: "Distance (SpotLight)",
+ value: "distance",
+ },
+ {
+ text: "Decay (SpotLight)",
+ value: "decay",
+ },
+ {
+ text: "Penumbra (SpotLight)",
+ value: "penumbra",
+ },
+ {
+ text: "Angle/Size (SpotLight)",
+ value: "angle",
+ },
+ {
+ text: "Power (SpotLight)",
+ value: "power",
+ },
+ {
+ text: "Target Position (Directional/SpotLight)",
+ value: "target",
+ },
+ ],
+ },
+ },
+ };
+ }
+
+ addLight(args) {
+ const light = new THREE[args.TYPE](0xffffff, 1);
+
+ createObject(args.NAME, light, args.GROUP);
+ lights[args.NAME] = light;
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
+
+ light.castShadow = true;
+ if (light.type === "PointLight") return;
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0);
+ scene.add(light.target);
+
+ light.pos = new THREE.Vector3(0, 0, 0);
+
+ light.shadow.mapSize.width = 4096;
+ light.shadow.mapSize.height = 2048;
+
+ if (light.type === "SpotLight") {
+ light.decay = 0;
+ light.shadow.camera.near = 500;
+ light.shadow.camera.far = 4000;
+ light.shadow.camera.fov = 30;
+ }
+ light.shadow.needsUpdate = true;
+ light.needsUpdate = true;
+ }
+
+ setLight(args) {
+ const light = lights[args.NAME];
+ if (!args.PROPERTY) return;
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)); //vector3
+ light.target.updateMatrixWorld();
+ } else {
+ light[args.PROPERTY] = getAsset(args.VALUE);
+ }
+ light.needsUpdate = true;
+
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
+
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
+ }
+ Scratch.extensions.register(new ThreeLights());
+
+ class ThreeUtilities {
+ getInfo() {
+ return {
+ id: "threeUtility",
+ name: "Three Utilities",
+ color1: "#3875c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newVector2",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ {
+ opcode: "newVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y] [Z]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Z: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "operateV3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "do [V3] [O] [V32]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ O: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "operators",
+ },
+ V32: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "moveVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "move [S] steps in vector [V3] in direction [D3]",
+ arguments: {
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "directionTo",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "direction from [V3] to [T3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ T3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "newColor",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Color [HEX]",
+ arguments: {
+ HEX: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ },
+ },
+ },
+ {
+ opcode: "newFog",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Fog [COLOR] [NEAR] [FAR]",
+ arguments: {
+ COLOR: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ exemptFromNormalization: true,
+ },
+ NEAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ FAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 10,
+ },
+ },
+ },
+ {
+ opcode: "newTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newCubeTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUMEX0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEX1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newEquirectangularTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Equirectangular Texture [COSTUME] [MODE]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "curve",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
+ arguments: {
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "curveTypes",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
+ },
+ CLOSED: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "mouseDown",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "mouse [BUTTON] [action]?",
+ arguments: {
+ BUTTON: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseButtons",
+ },
+ action: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseAction",
+ },
+ },
+ },
+ {
+ opcode: "mousePos",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "mouse position",
+ arguments: {},
+ },
+ "---",
+ {
+ opcode: "getItem",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get item [ITEM] of [ARRAY]",
+ arguments: {
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ ARRAY: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: `["myObject", "myLight"]`,
+ },
+ },
+ },
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Raycasting",
+ },
+ {
+ opcode: "raycast",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Raycast from [V3] in direction [D3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,1]",
+ },
+ },
+ },
+ {
+ opcode: "getRaycast",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get raycast [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "raycastProperties",
+ },
+ },
+ },
+ ],
+ menus: {
+ materialProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map (texture)",
+ value: "map",
+ },
+ {
+ text: "Alpha Map (texture)",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test (0-1)",
+ value: "alphaTest",
+ },
+ {
+ text: "Side (front/back/double)",
+ value: "side",
+ },
+ {
+ text: "Bump Map (texture)",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ raycastProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Intersected Object Names",
+ value: "name",
+ },
+ {
+ text: "Number of Objects",
+ value: "number",
+ },
+ {
+ text: "Intersected Objects distances",
+ value: "distance",
+ },
+ ],
+ },
+ mouseButtons: {
+ acceptReporters: false,
+ items: ["left", "middle", "right"],
+ },
+ mouseAction: {
+ acceptReporters: false,
+ items: ["Down", "Clicked"],
+ },
+ curveTypes: {
+ acceptReporters: false,
+ items: ["CatmullRomCurve3"],
+ },
+ operators: {
+ acceptReporters: false,
+ items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
+ },
+ },
+ };
+ }
+ mouseDown(args) {
+ if (args.action === "Down") return isMouseDown[args.BUTTON];
+ if (args.action === "Clicked") {
+ if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
+ else prevMouse[args.BUTTON] = true;
+ return true;
+ }
+ }
+ mousePos(event) {
+ return JSON.stringify(mouseNDC);
+ }
+ newVector3(args) {
+ return JSON.stringify([args.X, args.Y, args.Z]);
+ }
+ operateV3(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const v32 = new THREE.Vector3(...JSON.parse(args.V32));
+
+ let r;
+ if (args.O == "+") r = v3.add(v32);
+ else if (args.O == "-") r = v3.sub(v32);
+ else if (args.O == "*") r = v3.multiply(v32);
+ else if (args.O == "/") r = v3.divide(v32);
+ else if (args.O == "=") r = v3.equals(v32);
+ else if (args.O == "max") r = v3.max(v32);
+ else if (args.O == "min") r = v3.min(v32);
+ else if (args.O == "dot") r = v3.dot(v32);
+ else if (args.O == "cross") r = v3.cross(v32);
+ else if (args.O == "distance to") r = v3.distanceTo(v32);
+ else if (args.O == "angle to") r = v3.angleTo(v32);
+ else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
+
+ if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
+ else return JSON.stringify(r);
+ }
+
+ newVector2(args) {
+ return JSON.stringify([args.X, args.Y]);
+ }
+
+ moveVector3(args) {
+ const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
+ const steps = Number(args.S);
+
+ const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
+
+ const yaw = THREE.MathUtils.degToRad(yawInputDeg);
+ const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
+ const roll = THREE.MathUtils.degToRad(rollInputDeg);
+
+ const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
+
+ const forwardVector = new THREE.Vector3(0, 0, -1);
+ const direction = forwardVector.applyEuler(euler).normalize();
+
+ const newPos = currentPos.add(direction.multiplyScalar(steps));
+ return JSON.stringify([newPos.x, newPos.y, newPos.z]);
+ }
+
+ directionTo(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
+
+ const direction = toV3.clone().sub(v3).normalize();
+ // Pitch (X)
+ const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
+ // Yaw (Y)
+ const yaw = Math.atan2(direction.x, direction.z);
+
+ // Roll always 0
+ return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
+ }
+
+ newColor(args) {
+ const color = new THREE.Color(args.HEX);
+ const uuid = crypto.randomUUID();
+ assets.colors[uuid] = color;
+ return `colors/${uuid}`;
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
+ const uuid = crypto.randomUUID();
+ assets.fogs[uuid] = fog;
+ return `fogs/${uuid}`;
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newCubeTexture(args) {
+ const uris = [
+ encodeCostume(args.COSTUMEX0),
+ encodeCostume(args.COSTUMEX1),
+ encodeCostume(args.COSTUMEY0),
+ encodeCostume(args.COSTUMEY1),
+ encodeCostume(args.COSTUMEZ0),
+ encodeCostume(args.COSTUMEZ1),
+ ];
+ const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
+
+ texture.name = "CubeTexture" + args.COSTUMEX0;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+ texture.mapping = THREE.EquirectangularReflectionMapping;
+
+ setTexutre(texture, args.MODE);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+
+ curve(args) {
+ function parsePoints(input) {
+ // Match all [x,y,z] groups
+ const matches = input.match(/\[([^\]]+)\]/g);
+ if (!matches) return [];
+
+ return matches.map((str) => {
+ const nums = str
+ .replace(/[\[\]\s]/g, "")
+ .split(",")
+ .map(Number);
+ return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
+ });
+ }
+ const points = parsePoints(args.POINTS);
+ const curve = new THREE[args.TYPE](points);
+ curve.closed = JSON.parse(args.CLOSED);
+
+ const uuid = crypto.randomUUID();
+ assets.curves[uuid] = curve;
+ return `curves/${uuid}`;
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY);
+ const item = items[args.ITEM - 1];
+ if (!item) return "0";
+ return item;
+ }
+
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3));
+ // rotation is in degrees => convert to radians first
+ const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
+
+ const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
+ const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
+
+ const raycaster = new THREE.Raycaster();
+ //const camera = getObject(args.CAMERA)
+ raycaster.set(origin, direction);
+
+ const intersects = raycaster.intersectObjects(scene.children, true);
+
+ raycastResult = intersects;
+ }
+ getRaycast(args) {
+ if (args.PROPERTY === "number") return raycastResult.length;
+ if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
+ return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
+ }
+ }
+ Scratch.extensions.register(new ThreeUtilities());
+
+ class ThreeGLB {
+ getInfo() {
+ return {
+ id: "threeGLB",
+ name: "Three GLB Loader",
+ color1: "#c53838ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load GLB File",
+ func: "loadModelFile",
+ },
+ {
+ opcode: "addModel",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add [ITEM] as [NAME] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ },
+ },
+ {
+ opcode: "getModel",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get object [PROPERTY] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelProperties",
+ },
+ },
+ },
+ {
+ opcode: "playAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "play animation [ANAME] of [NAME], [TIMES] times",
+ arguments: {
+ TIMES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "pauseAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [TOGGLE] animation [ANAME] of [NAME]",
+ arguments: {
+ TOGGLE: {
+ type: Scratch.ArgumentType.NUMBER,
+ menu: "pauseUn",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "stopAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stop animation [ANAME] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ modelProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Animations",
+ value: "animations",
+ }, ],
+ },
+ pauseUn: {
+ acceptReporters: true,
+ items: [{
+ text: "Pause",
+ value: "true",
+ },
+ {
+ text: "Unpasue",
+ value: "false",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ async loadModelFile() {
+ openFileExplorer(".glb").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ {
+ // From lily's assets
+
+ // Thank you PenguinMod for providing this code.
+ {
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ //const res = await Scratch.fetch(args.URL);
+ //const buffer = await res.arrayBuffer();
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Model loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading model.");
+ }
+ }
+ // End of PenguinMod
+ }
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ async addModel(args) {
+ const group = await getModel(args.ITEM, args.NAME);
+
+ createObject(args.NAME, group, args.GROUP);
+ }
+ getModel(args) {
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString();
+ }
+
+ playAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) {
+ console.log("no model!");
+ return;
+ }
+
+ const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
+ if (!action) {
+ console.log("no action!");
+ return;
+ }
+
+ args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
+
+ action.reset().play();
+ }
+ stopAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.stop();
+ }
+ pauseAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.paused = args.TOGGLE;
+ }
+ }
+ Scratch.extensions.register(new ThreeGLB());
+
+ class ThreeAddons {
+ getInfo() {
+ return {
+ id: "threeAddons",
+ name: "Three Addons",
+ color1: "#c538a2ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.LABEL,
+ text: "Orbit Control",
+ },
+ {
+ opcode: "OrbitControl",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set addon Orbit Control [STATE]",
+ arguments: {
+ STATE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "onoff",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "Post Processing",
+ },
+ {
+ opcode: "resetComposer",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset composer",
+ },
+ {
+ opcode: "bloom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ I: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ T: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "godRays",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ DEC: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.95,
+ },
+ DENS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ EXP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ WEI: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.4,
+ },
+ RES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ SAMP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 64,
+ },
+ },
+ },
+ {
+ opcode: "dots",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ A: {
+ type: Scratch.ArgumentType.ANGLE,
+ defaultValue: 0,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "depth",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ FD: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 3,
+ },
+ FL: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.001,
+ },
+ BS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 4,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 240,
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "custom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myShader",
+ },
+ FRA: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ VER: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ ],
+ menus: {
+ onoff: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "1",
+ },
+ {
+ text: "disabled",
+ value: "0",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [
+ "SKIP",
+ "SET",
+ "ADD",
+ "ALPHA",
+ "AVERAGE",
+ "COLOR",
+ "COLOR_BURN",
+ "COLOR_DODGE",
+ "DARKEN",
+ "DIFFERENCE",
+ "DIVIDE",
+ "DST",
+ "EXCLUSION",
+ "HARD_LIGHT",
+ "HARD_MIX",
+ "HUE",
+ "INVERT",
+ "INVERT_RGB",
+ "LIGHTEN",
+ "LINEAR_BURN",
+ "LINEAR_DODGE",
+ "LINEAR_LIGHT",
+ "LUMINOSITY",
+ "MULTIPLY",
+ "NEGATION",
+ "NORMAL",
+ "OVERLAY",
+ "PIN_LIGHT",
+ "REFLECT",
+ "SCREEN",
+ "SRC",
+ "SATURATION",
+ "SOFT_LIGHT",
+ "SUBTRACT",
+ "VIVID_LIGHT",
+ ],
+ },
+ },
+ };
+ }
+
+ OrbitControl(args) {
+ if (controls) controls.dispose();
+
+ console.log("creating...", OrbitControls);
+ controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
+ controls.enableDamping = true;
+
+ controls.enabled = !!args.STATE;
+ console.log(controls);
+ }
+
+ resetComposer() {
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
+
+ bloom(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ bloomEffect.blendMode.opacity.value = args.OP;
+
+ const pass = new EffectPass(camera, bloomEffect);
+
+ composer.addPass(pass);
+ }
+
+ godRays(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ let object = getObject(args.NAME);
+ const sun = object;
+
+ const godRays = new GodRaysEffect(camera, sun, {
+ resolutionScale: args.RES,
+ density: args.DENS, // ray density
+ decay: args.DEC, // fade out
+ weight: args.WEI, // brightness of rays
+ exposure: args.EXP,
+ samples: args.SAMP,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ godRays.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, godRays);
+ composer.addPass(pass);
+ }
+
+ dots(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dot.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, dot);
+ composer.addPass(pass);
+ }
+
+ depth(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dofEffect.blendMode.opacity.value = args.OP;
+
+ const dofPass = new EffectPass(camera, dofEffect);
+ composer.addPass(dofPass);
+ }
+
+ async custom(args) {
+ function cleanGLSL(glslCode) {
+ //delete multilines comments
+ let cleanedCode = glslCode
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
+ .replace(/ /g, "\n")
+ .replace(/\/\/.*$/gm, " ")
+ .replace(/; /g, ";\n");
+
+ return cleanedCode;
+ }
+
+ let fs = cleanGLSL(`
+ ${args.FRA}
+ `);
+ if (!args.FRA.trim()) {
+ fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
+ }
+ const vs = cleanGLSL(`
+ ${args.VER}
+ `);
+ console.log(fs);
+ console.log(vs);
+
+ const effect = new Effect("Custom", fs, {
+ blendFunction: BlendFunction[args.BLEND],
+ vertexShader: vs,
+ uniforms: new Map([
+ //uniforms usually in shaders... open to more!
+ ["time", new THREE.Uniform(0.0)],
+ [
+ "resolution",
+ new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
+ ],
+ ]),
+ defines: new Map([
+ ["USE_TIME", "1"],
+ ["USE_VERTEX_TRANSFORM", ""],
+ ]),
+ });
+
+ effect.blendMode.opacity.value = args.OP;
+
+ const pass = new EffectPass(camera, effect);
+ composer.addPass(pass);
+
+ customEffects.push(effect);
+ }
+ }
+ Scratch.extensions.register(new ThreeAddons());
+
+ class RapierPhysics {
+ getInfo() {
+ return {
+ id: "rapierPhysics",
+ name: "RAPIER Physics",
+ color1: "#222222",
+ color2: "#203024ff",
+ color3: "#78f07eff",
+ blocks: [{
+ opcode: "createWorld",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create world | gravity:[G]",
+ arguments: {
+ G: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,-9.81,0]",
+ },
+ },
+ },
+ {
+ opcode: "getWorld",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get world [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "wProp",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "objectPhysics",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
+ arguments: {
+ state2: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state2",
+ },
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ type: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ defaultValue: "dynamic",
+ },
+ collider: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderTypes",
+ defaultValue: "cuboid",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ mass: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ density: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ friction: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0.5",
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- RigidBody",
+ },
+ {
+ opcode: "setRB",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodySets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getRB",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get rigidbody [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodyProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "lockObjectAxis",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
+ arguments: {
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lockAxes",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Y: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Z: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "addForce",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,10,0]",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "forces",
+ defaultValue: "addForce",
+ },
+ SPACE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "spaces",
+ defaultValue: "world",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "resetForces",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "resetF",
+ defaultValue: "resetForces",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "enableCCD",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable Continuous Collision Detection for [OBJECT] [state]",
+ arguments: {
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "oPropS",
+ defaultValue: "physics",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "fixedJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ RA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ RB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "sphericalJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ },
+ },
+ {
+ opcode: "revoluteJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- Collider",
+ },
+ {
+ opcode: "setC",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderSets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getC",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get collider [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "sensorSingle",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is sensor [SENSOR] touching [OBJECT]?",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "sensorAll",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "objects touching sensor [SENSOR]",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ },
+ },
+ ],
+ menus: {
+ wProp: {
+ acceptReporters: false,
+ items: [{
+ text: "Gravity",
+ value: "gravity",
+ },
+ {
+ text: "log to console",
+ value: "log",
+ },
+ ],
+ },
+ tf: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true",
+ value: "true",
+ },
+ ],
+ },
+ lockAxes: {
+ acceptReporters: false,
+ items: [{
+ text: "Translation",
+ value: "setEnabledTranslations",
+ },
+ {
+ text: "Rotation",
+ value: "setEnabledRotations",
+ },
+ ],
+ },
+ rigidBodyProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Type",
+ value: "bodyType",
+ },
+ {
+ text: "Linear Velocity",
+ value: "linvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "angvel",
+ },
+ {
+ text: "Translation (position)",
+ value: "translation",
+ },
+ {
+ text: "Rotation (quaternion)",
+ value: "rotation",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ //{text: "Center of Mass", value: "centerOfMass"},
+ {
+ text: "Linear Damping",
+ value: "linearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "angularDamping",
+ },
+ {
+ text: "Is Sleeping?",
+ value: "isSleeping",
+ },
+ //{text: "Can Sleep?", value: "isCanSleep"},
+ {
+ text: "Gravity Scale",
+ value: "gravityScale",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ //{text: "Sleeping", value: "sleeping"}
+ ],
+ },
+ rigidBodySets: {
+ acceptReporters: false,
+ items: [
+ //{text: "Linear Velocity", value: "setLinvel"},
+ //{text: "Angular Velocity", value: "setAngvel"},
+ //{text: "Mass", value: "setMass"},
+ {
+ text: "Gravity Scale",
+ value: "setGravityScale",
+ },
+ //{text: "Can Sleep?", value: "setCanSleep"},
+ //{text: "Sleeping", value: "sleeping"},
+ {
+ text: "Linear Damping",
+ value: "setLinearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "setAngularDamping",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ ],
+ },
+ colliderProperties: {
+ acceptReporters: false,
+ items: [
+ //{text: "Collider Type", value: "type"},
+ {
+ text: "Is Sensor?",
+ value: "isSensor",
+ },
+ {
+ text: "Friction",
+ value: "friction",
+ },
+ {
+ text: "Restitution",
+ value: "restitution",
+ },
+ {
+ text: "Density",
+ value: "density",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ {
+ text: "Position",
+ value: "translation",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ //{text: "Area", value: "area"},
+ {
+ text: "Volume",
+ value: "volume",
+ },
+ {
+ text: "Collision Groups",
+ value: "collisionGroups",
+ },
+ //{text: "Collision Mask", value: "collisionMask"},
+ //{text: "Is Enabled?", value: "enabled"},
+ //{text: "Contact Count", value: "contactCount"},
+ //{text: "RigidBody Handle", value: "rigidBody"}
+ ],
+ },
+ colliderSets: {
+ acceptReporters: false,
+ items: [{
+ text: "Friction",
+ value: "setFriction",
+ },
+ {
+ text: "Restitution",
+ value: "setRestitution",
+ },
+ {
+ text: "Density",
+ value: "setDensity",
+ },
+ {
+ text: "Is Sensor?",
+ value: "setSensor",
+ },
+ {
+ text: "Collision Groups",
+ value: "setCollisionGroups",
+ },
+ //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
+ //{text: "Position Offset", value: "setTranslation"},
+ //{text: "Rotation Offset", value: "setRotation"}
+ ],
+ },
+ state: {
+ acceptReporters: true,
+ items: [{
+ text: "on",
+ value: "true",
+ },
+ {
+ text: "off",
+ value: "false",
+ },
+ ],
+ },
+ state2: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true (must be fixed)",
+ value: "true",
+ },
+ ],
+ },
+ spaces: {
+ acceptReporters: false,
+ items: [{
+ text: "World",
+ value: "world",
+ },
+ {
+ text: "Local",
+ value: "local",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Dynamic",
+ value: "dynamic",
+ },
+ {
+ text: "Fixed",
+ value: "fixed",
+ },
+ {
+ text: "Kinematic Position Based",
+ value: "kinematicPositionBased",
+ },
+ ],
+ },
+ colliderTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box, Rectangle, cuboid",
+ value: "cuboid",
+ },
+ {
+ text: "Sphere, ball",
+ value: "ball",
+ },
+ {
+ text: "Custom, complex simple shapes, convexHull",
+ value: "convexHull",
+ },
+ {
+ text: "Precision, TriMesh",
+ value: "trimesh",
+ },
+ ],
+ },
+ forces: {
+ acceptReporters: false,
+ items: [{
+ text: "Force",
+ value: "addForce",
+ },
+ {
+ text: "Torque (rotation)",
+ value: "addTorque",
+ },
+ {
+ text: "Apply Impulse",
+ value: "applyImpulse",
+ },
+ {
+ text: "Apply Torque Impulse (rotation)",
+ value: "applyTorqueImpulse",
+ },
+ {
+ text: "Linear Velocity",
+ value: "setLinvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "setAngvel",
+ },
+ ],
+ },
+ resetF: {
+ acceptReporters: false,
+ items: [{
+ text: "Forces",
+ value: "resetForces",
+ },
+ {
+ text: "Torques",
+ value: "resetTorques",
+ },
+ ],
+ },
+ },
+ };
+ }
+ joint(jointData, bodyA, bodyB) {
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
+ }
+
+ fixedJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ let RA = JSON.parse(args.RA).map(Number);
+ let RB = JSON.parse(args.RB).map(Number);
+
+ RA = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RA[0]),
+ THREE.MathUtils.degToRad(RA[1]),
+ THREE.MathUtils.degToRad(RA[2])
+ )
+ );
+ RB = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RB[0]),
+ THREE.MathUtils.degToRad(RB[1]),
+ THREE.MathUtils.degToRad(RB[2])
+ )
+ );
+
+ const data = RAPIER.JointData.fixed({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ },
+ RA, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ },
+ RB
+ );
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ sphericalJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+
+ const data = RAPIER.JointData.spherical({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ revoluteJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.revolute({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ prismaticJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.prismatic({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ createWorld(args) {
+ const v3 = JSON.parse(args.G).map(Number);
+ const gravity = {
+ x: v3[0],
+ y: v3[1],
+ z: v3[2],
+ };
+ physicsWorld = new RAPIER.World(gravity);
+
+ console.log(physicsWorld);
+ }
+
+ getWorld(args) {
+ if (args.PROPERTY === "log") {
+ console.log(physicsWorld);
+ return "logged";
+ }
+ return JSON.stringify(physicsWorld[args.PROPERTY]);
+ }
+
+ setRB(args) {
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.rigidBody[args.PROPERTY](value);
+ }
+ setC(args) {
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.collider[args.PROPERTY](value);
+ }
+
+ getRB(args) {
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.rigidBody[args.PROPERTY]());
+ }
+ getC(args) {
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.collider[args.PROPERTY]());
+ }
+
+ lockObjectAxis(args) {
+ let object = getObject(args.OBJECT);
+ const x = !JSON.parse(args.X);
+ const y = !JSON.parse(args.Y);
+ const z = !JSON.parse(args.Z);
+ object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
+ }
+
+ objectPhysics(args) {
+ let object = getObject(args.OBJECT);
+ object.physics = JSON.parse(args.state);
+
+ if (JSON.parse(args.state)) {
+ //if already exists delete:
+ if (object.rigidBody) {
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ /*asing a rigidbody and collider to object and add them to physicsWorld*/
+ let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
+ .setTranslation(object.position.x, object.position.y, object.position.z)
+ .setRotation({
+ w: object.quaternion._w,
+ x: object.quaternion._x,
+ y: object.quaternion._y,
+ z: object.quaternion._z,
+ });
+
+ let colliderDesc;
+ switch (args.collider) {
+ case "cuboid":
+ colliderDesc = createCuboidCollider(object);
+ break;
+ case "ball":
+ colliderDesc = createBallCollider(object);
+ break;
+ case "convexHull":
+ colliderDesc = createConvexHullCollider(object);
+ break;
+ case "trimesh":
+ colliderDesc = TriMesh(object);
+ break;
+ }
+ colliderDesc
+ .setSensor(JSON.parse(args.state2))
+ .setMass(args.mass)
+ .setDensity(args.density)
+ .setFriction(args.friction);
+
+ let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
+ let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
+
+ object.rigidBody = rigidBody;
+ object.collider = collider;
+ } else {
+ /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ }
+
+ enableCCD(args) {
+ let object = getObject(args.OBJECT);
+ if (object.physics) {
+ let rigidBody = object.rigidBody;
+ rigidBody.enableCcd(JSON.parse(args.state));
+ }
+ }
+
+ addForce(args) {
+ let object = getObject(args.OBJECT);
+ const vector = JSON.parse(args.VALUE).map(Number);
+
+ let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
+ if (args.SPACE === "local") {
+ force.applyQuaternion(object.quaternion);
+ }
+
+ object.rigidBody[args.PROPERTY](force, true);
+ }
+
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true);
+ }
+
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR);
+
+ let object = getObject(args.OBJECT);
+
+ let touching = false;
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ if (otherCollider === object.collider) touching = true;
+ });
+
+ return touching;
+ }
+
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR);
+
+ const touchedObjects = [];
+
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ // find owner of collider
+ const otherObject = scene.children.find((o) => o.collider === otherCollider);
+ console.log(otherCollider);
+ if (otherObject) touchedObjects.push(otherObject.name);
+ });
+
+ return JSON.stringify(touchedObjects);
+ }
+ }
+ Scratch.extensions.register(new RapierPhysics());
+
+ //Thanks to the PointerLock extension of Turbowarp
+ const mouse = vm.runtime.ioDevices.mouse;
+ let isLocked = false;
+ let isPointerLockEnabled = false;
+
+ let rect = threeRenderer.domElement.getBoundingClientRect();
+ document.addEventListener("resize", () => {
+ rect = threeRenderer.domElement.getBoundingClientRect();
+ });
+
+ const postMouseData = (e, isDown) => {
+ const {
+ movementX,
+ movementY
+ } = e;
+ const {
+ width,
+ height
+ } = rect;
+ const x = mouse._clientX + movementX;
+ const y = mouse._clientY - movementY;
+ mouse._clientX = x;
+ mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
+ mouse._clientY = y;
+ mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
+ if (typeof isDown === "boolean") {
+ const data = {
+ button: e.button,
+ isDown,
+ };
+ originalPostIOData(data);
+ }
+ };
+
+ const mouseDevice = vm.runtime.ioDevices.mouse;
+ const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
+ mouseDevice.postData = (data) => {
+ if (!isPointerLockEnabled) {
+ return originalPostIOData(data);
+ }
+ };
+
+ document.addEventListener(
+ "mousedown",
+ (e) => {
+ // @ts-expect-error
+ if (threeRenderer.domElement.contains(e.target)) {
+ if (isLocked) {
+ postMouseData(e, true);
+ } else if (isPointerLockEnabled) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mouseup",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e, false);
+ // @ts-expect-error
+ } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mousemove",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e);
+ }
+ },
+ true
+ );
+
+ document.addEventListener("pointerlockchange", () => {
+ isLocked = document.pointerLockElement === threeRenderer.domElement;
+ });
+ document.addEventListener("pointerlockerror", (e) => {
+ console.error("Pointer lock error", e);
+ });
+
+ const oldStep = vm.runtime._step;
+ vm.runtime._step = function(...args) {
+ const ret = oldStep.call(this, ...args);
+ if (isPointerLockEnabled) {
+ const {
+ width,
+ height
+ } = rect;
+ mouse._clientX = width / 2;
+ mouse._clientY = height / 2;
+ mouse._scratchX = 0;
+ mouse._scratchY = 0;
+ }
+ return ret;
+ };
+
+ vm.runtime.on("PROJECT_LOADED", () => {
+ isPointerLockEnabled = false;
+ if (isLocked) {
+ document.exitPointerLock();
+ }
+ });
+
+ class Pointerlock {
+ getInfo() {
+ return {
+ id: "threepointerlockmod",
+ name: "Pointerlock for Extra 3D",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setLocked",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set pointer lock [enabled]",
+ arguments: {
+ enabled: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ menu: "enabled",
+ },
+ },
+ },
+ {
+ opcode: "isLocked",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "pointer locked?",
+ },
+ ],
+ menus: {
+ enabled: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "true",
+ },
+ {
+ text: "disabled",
+ value: "false",
+ },
+ ],
+ },
+ },
+ };
+ }
+
+ setLocked(args) {
+ isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
+ if (!isPointerLockEnabled && isLocked) {
+ document.exitPointerLock();
+ }
+ }
+
+ isLocked() {
+ return isLocked;
+ }
+ }
+ Scratch.extensions.register(new Pointerlock());
+ });
+})(Scratch);
diff --git a/threejsD_BASE_13852.js b/threejsD_BASE_13852.js
new file mode 100644
index 0000000..ced1928
--- /dev/null
+++ b/threejsD_BASE_13852.js
@@ -0,0 +1,2413 @@
+// Name: Extra 3D
+// ID: threejsExtension
+// Description: Use three js inside Turbowarp! A 3D graphics library.
+// By: Civero
+// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
+
+(function (Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Three-D extension must run unsandboxed");
+ }
+
+ if (Scratch.vm.runtime.isPackaged) {alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`); return}
+ //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
+
+ const vm = Scratch.vm;
+ const runtime = vm.runtime
+ const renderer = Scratch.renderer;
+ const canvas = renderer.canvas
+ const Cast = Scratch.Cast;
+ const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
+
+ let alerts = false
+ console.log("alerts are "+ (alerts ? "enabled" : "disabled"))
+
+ let isMouseDown = { left: false, middle: false, right: false }
+ let prevMouse = { left: false, middle: false, right: false }
+
+ let lastWidth = 0
+ let lastHeight = 0
+
+ let THREE
+ let clock
+ let running
+ let loopId
+ //Addons
+ let GLTFLoader
+ let gltf
+ let OrbitControls
+ let controls
+ let BufferGeometryUtils
+ let TextGeometry
+ let fontLoad
+ //Physics
+ let RAPIER
+ let physicsWorld
+
+ let threeRenderer
+ let scene
+ let camera
+ let eulerOrder = "YXZ"
+
+ let composer
+ let passes = {}
+ let customEffects = []
+ let renderTargets = {}
+
+ let materials = {}
+ let geometries = {}
+ let lights = {}
+ let models = {}
+
+ let assets = { //should i place materials, geometries; inside too?
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {}, //not the same as the global one! this one only stores textures
+ }
+
+ let raycastResult = []
+
+ function resetor(level) {
+ camera = undefined
+ composer.reset()
+
+ passes = {}
+ customEffects = []
+ renderTargets = {}
+
+ materials = {}
+ geometries = {}
+ lights = {}
+ models = {}
+
+ if (level > 0) {
+ assets = {
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {},
+ }
+ }
+
+ updateComposers()
+ }
+
+//utility
+ function vector3ToString(prop) {
+ if (!prop) return "0,0,0";
+
+ const x = (typeof(prop.x) === "number") ? prop.x : (typeof(prop._x) === "number") ? prop._x : (JSON.stringify(prop).includes("X")) ? prop : 0
+ const y = (typeof(prop.y) === "number") ? prop.y : (typeof(prop.y) === "number") ? prop._y : 0
+ const z = (typeof(prop.z) === "number") ? prop.z : (typeof(prop.z) === "number") ? prop.z : 0
+
+ return [x, y, z]
+ }
+
+//objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true)
+ if (object) {
+ removeObject(name)
+ alerts ? alert(name + " already exsisted, will replace!") : null
+ }
+ content.name = name
+ content.rotation._order = eulerOrder
+ parentName === scene.name ? object = scene : object = getObject(parentName)
+ content.physics = false
+
+ object.add(content)
+ }
+ function removeObject(name) {
+ let object = getObject(name)
+ if (!object) return
+
+ scene.remove(object)
+
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true)
+ physicsWorld.removeRigidBody(object.rigidBody, true)
+ object.rigidBody = null
+ object.collider = null
+ }
+ if (object.isLight) {
+ delete(lights[name])
+ }
+ }
+ function getObject(name, isNew) {
+ let object = null
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null; return;}
+ object = scene.getObjectByName(name)
+ if (!object && !isNew) {alerts ? alert(name + " does not exist! Add it to scene"):null; return;}
+ return object
+ }
+
+//materials
+ function encodeCostume (name) {
+ if (name.startsWith("data:image/")) return name
+ return Scratch.vm.editingTarget.sprite.costumes.find(c => c.name === name).asset.encodeDataURI()
+ }
+ function setTexutre (texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace
+
+ if (mode === "Pixelate") {
+ texture.minFilter = THREE.NearestFilter;
+ texture.magFilter = THREE.NearestFilter;
+ } else { //Blur
+ texture.minFilter = THREE.NearestMipmapLinearFilter
+ texture.magFilter = THREE.NearestMipmapLinearFilter
+ }
+
+ if (style === "Repeat") {
+ texture.wrapS = THREE.RepeatWrapping
+ texture.wrapT = THREE.RepeatWrapping
+ texture.repeat.set(x, y)
+ }
+
+ texture.generateMipmaps = true;
+ }
+ async function resizeImageToSquare(uri, size = 256) {
+ return new Promise((resolve) => {
+ const img = new Image()
+ img.onload = () => {
+ const canvas = document.createElement('canvas')
+ canvas.width = size
+ canvas.height = size
+ const ctx = canvas.getContext('2d')
+
+ // clear + draw image scaled to fit canvas
+ ctx.clearRect(0, 0, size, size)
+ ctx.drawImage(img, 0, 0, size, size)
+
+ resolve(canvas.toDataURL()) // return normalized Data URI
+ //delete canvas?
+ };
+ img.src = uri
+ });
+}
+//light
+function updateShadowFrustum(light, focusPos) {
+ if (light.type !== "DirectionalLight") return
+
+ // Frustum Size - Increase this value to cover a larger area.
+ const d = 50;
+
+ // Update Orthographic Shadow Camera Frustum
+ const shadowCamera = light.shadow.camera;
+
+ // Set the width/height of the frustum
+ shadowCamera.left = -d;
+ shadowCamera.right = d;
+ shadowCamera.top = d;
+ shadowCamera.bottom = -d;
+
+ // Determine ranges
+ shadowCamera.near = 0.1
+ shadowCamera.far = 500
+
+ // Position the Light and its Target
+ light.target.position.copy(focusPos);
+ const direction = light.position.clone().sub(light.target.position).normalize();
+ light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
+
+ // Ensure matrices are updated.
+ light.target.updateMatrixWorld();
+ light.shadow.camera.updateProjectionMatrix()
+ light.shadow.needsUpdate = true;
+}
+//composer
+function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
+
+ // always recreate the RenderPass to point to the current scene/camera
+ passes["Render"] = new RenderPass(scene, camera);
+
+ // ensure composer has a RenderPass as the first pass
+ const hasRender = composer.passes.some(p => p && p.scene);
+ if (!hasRender) composer.addPass(passes["Render"]);
+ else {
+ // if composer already has one, replace it so it references current scene/camera
+ const idx = composer.passes.findIndex(p => p && p.scene);
+ composer.passes[idx] = passes["Render"];
+ }
+}
+//utility
+function getMouseNDC(event) {
+ // Use threeRenderer.domElement for correct offset
+ const rect = threeRenderer.domElement.getBoundingClientRect();
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ return [x, y];
+}
+function checkCanvasSize() {
+ const { width, height } = canvas
+ if (width !== lastWidth || height !== lastHeight) {
+ lastWidth = width
+ lastHeight = height
+ resize()
+ }
+ requestAnimationFrame(checkCanvasSize) //rerun next frame
+}
+//physics
+function computeWorldBoundingBox(mesh) {
+ // Create a Box3 in world coordinates
+ const box = new THREE.Box3().setFromObject(mesh);
+ const size = new THREE.Vector3();
+ box.getSize(size);
+ const center = new THREE.Vector3();
+ box.getCenter(center);
+ return { size, center };
+}
+function createCuboidCollider(mesh) {
+ const { size } = computeWorldBoundingBox(mesh);
+ const collider = RAPIER.ColliderDesc.cuboid(
+ size.x / 2,
+ size.y / 2,
+ size.z / 2
+ )
+ return collider;
+}
+function createBallCollider(mesh) {
+ const { size } = computeWorldBoundingBox(mesh);
+ // radius = 1/2 of the largest verticie
+ const radius = Math.max(size.x, size.y, size.z) / 2;
+ const collider = RAPIER.ColliderDesc.ball(radius)
+ return collider //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
+}
+function createConvexHullCollider(mesh) {
+ mesh.updateWorldMatrix(true, false);
+
+ const position = mesh.geometry.attributes.position;
+ const vertices = [];
+ const vertex = new THREE.Vector3();
+
+ // Matrix for scale only
+ const scaleMatrix = new THREE.Matrix4().makeScale(
+ mesh.scale.x,
+ mesh.scale.y,
+ mesh.scale.z
+ );
+
+ for (let i = 0; i < position.count; i++) {
+ vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
+ vertices.push(vertex.x, vertex.y, vertex.z);
+ }
+
+ const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
+ return collider;
+}
+function TriMesh(mesh) {
+ // Get the positions array (from your geoPoints function)
+const positions = mesh.geometry.attributes.position.array;
+const numVertices = positions.length / 3;
+
+// Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
+const indices = Array.from({ length: numVertices }, (_, i) => i);
+
+const collider = RAPIER.ColliderDesc.trimesh(
+ positions,
+ new Uint32Array(indices)
+);
+
+return collider
+}
+function getModel(model, name) {
+ const file = runtime.getTargetForStage().getSounds().find(c => c.name === model)
+ if (!file) return
+
+return new Promise((resolve, reject) => {
+ gltf.parse(
+ file.asset.data.buffer,
+ "",
+ gltf => {
+ const root = gltf.scene
+ root.traverse(child => {
+ if (child.isMesh) {
+ child.castShadow = true
+ child.receiveShadow = true
+ }
+ });
+
+ const mixer = new THREE.AnimationMixer(root)
+ const actions = {}
+ gltf.animations.forEach(clip => {
+ const act = mixer.clipAction(clip)
+ act.clampWhenFinished = true
+ actions[clip.name] = act
+ });
+
+ models[name] = { root, mixer, actions }
+ resolve(root)
+ },
+ error => {
+ console.error("Error parsing GLB model:", error)
+ reject(error)
+ }
+ )})
+}
+async function openFileExplorer(format) {
+ return new Promise((resolve) => {
+ const input = document.createElement("input");
+ input.type = "file"
+ input.accept = format
+ input.multiple = false
+ input.onchange = () => {
+ resolve(input.files)
+ input.remove()
+ };
+ input.click();
+ })
+}
+function getMeshesUsingTexture(scene, targetTexture) {
+ const meshes = []
+
+ scene.traverse(object => {
+ if (object.material) {
+ const materials = Array.isArray(object.material) ? object.material : [object.material]
+ for (const material of materials) {
+ if (material.map === targetTexture) {
+ meshes.push(object)
+ break
+ }
+ }
+ }
+ })
+
+ return meshes
+}
+function getAsset(path) {
+ if (typeof(path) == "string") { //string?
+ if (path.includes("/")) { //has the /?
+ const value = path.split("/")
+ console.log(value[0], value[1])
+ return assets[value[0]][value[1]]
+ }
+ }
+
+ return JSON.parse(path) //boolean or number
+}
+
+let mouseNDC = [0, 0]
+//loops/init
+function stopLoop() {
+ if (!running) return
+ running = false
+
+ if (loopId) {
+ cancelAnimationFrame(loopId)
+ loopId = null
+ if (threeRenderer) threeRenderer.clear();
+ }
+}
+async function load() {
+ if (!THREE) {
+
+ // @ts-ignore
+ THREE = await import("https://esm.sh/three@0.180.0")
+ //Addons
+ GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js")
+ OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js")
+ BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js")
+ TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js")
+ const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js")
+ fontLoad = new FontLoader.FontLoader()
+
+ const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8")
+ const {
+ EffectComposer,
+ EffectPass,
+ RenderPass,
+
+ Effect,
+ BloomEffect,
+ GodRaysEffect,
+ DotScreenEffect,
+ DepthOfFieldEffect,
+
+ BlendFunction
+ } = POSTPROCESSING
+ //so i can use them later as global
+ window.EffectComposer = EffectComposer;
+ window.EffectPass = EffectPass;
+ window.RenderPass = RenderPass;
+ window.Effect = Effect;
+ window.BloomEffect = BloomEffect;
+ window.GodRaysEffect = GodRaysEffect;
+ window.DotScreenEffect = DotScreenEffect;
+ window.DepthOfFieldEffect = DepthOfFieldEffect;
+ window.BlendFunction = BlendFunction;
+
+ RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0")
+ await RAPIER.init()
+
+ threeRenderer = new THREE.WebGLRenderer({
+ powerPreference: "high-performance",
+ antialias: false,
+ stencil: false,
+ depth: true
+ })
+ threeRenderer.setPixelRatio(window.devicePixelRatio)
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping // HDR look (test)
+ //threeRenderer.toneMappingExposure = 1.0 //(test)
+
+ threeRenderer.shadowMap.enabled = true
+ threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap // (optional)
+ threeRenderer.domElement.style.pointerEvents = 'auto' //will disable turbowarp mouse events, but enable threejs's
+
+ gltf = new GLTFLoader.GLTFLoader()
+ clock = new THREE.Clock()
+
+ // Example: create a composer
+ composer = new EffectComposer(threeRenderer, {frameBufferType: THREE.HalfFloatType})
+
+ renderer.addOverlay( threeRenderer.domElement, "manual" )
+ renderer.addOverlay(canvas, "manual")
+ renderer.setBackgroundColor(1, 1, 1, 0)
+
+ resize()
+
+ window.addEventListener("mousedown", e => {
+ if (e.button === 0) isMouseDown.left = true
+ if (e.button === 1) isMouseDown.middle = true
+ if (e.button === 2) isMouseDown.right = true
+ })
+ window.addEventListener("mouseup", e => {
+ if (e.button === 0) isMouseDown.left = false; prevMouse.left = false
+ if (e.button === 1) isMouseDown.middle = false; prevMouse.middle = false
+ if (e.button === 2) isMouseDown.right = false; prevMouse.right = false
+ })
+ // prevent contextmenu on right click
+ threeRenderer.domElement.addEventListener("contextmenu", e => e.preventDefault());
+
+ threeRenderer.domElement.addEventListener('mousemove', (event) => {
+ mouseNDC = getMouseNDC(event);
+ })
+
+ running = false
+ load()
+
+ startRenderLoop()
+ runtime.on('PROJECT_START', () => startRenderLoop())
+ runtime.on('PROJECT_STOP_ALL', () => stopLoop())
+ runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
+ //if (!runtime.isPackaged) checkCanvasSize() //only in editor
+ }
+ }
+function startRenderLoop() {
+ if (running) return
+ running = true
+
+ const loop = () => {
+ if (!running) return
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step()
+
+ scene.children.forEach(obj => {
+ if (!(obj.isMesh) || !(obj.physics)) return
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation())
+ obj.quaternion.copy(obj.rigidBody.rotation())
+ }
+ })
+
+ }
+ if (scene && camera) {
+ if (controls) controls.update()
+
+ const delta = clock.getDelta()
+ Object.values(models).forEach( model => { if (model) model.mixer.update(delta) } )
+
+ Object.values(lights).forEach(light => updateShadowFrustum(light, camera.position))
+
+ //update custom effects time
+ customEffects.forEach(e => {
+ if (e.uniforms.get('time')) {
+ e.uniforms.get('time').value += delta
+ }
+ })
+ Object.values(renderTargets).forEach(t => {
+ if ( t.camera.type == "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height
+ t.camera.updateProjectionMatrix()
+ }
+ // get meshes using the texture associated with this target
+ const displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
+
+ displayMeshes.forEach(mesh => {
+ mesh.visible = false
+ })
+
+ if (t.camera.type == "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target)
+ threeRenderer.clear(true, true, true)
+ threeRenderer.render(scene, t.camera)
+ } else {
+ t.target.clear(threeRenderer)
+ t.camera.update( threeRenderer, scene ) //cubeCamera
+ }
+
+ displayMeshes.forEach(mesh => {
+ mesh.visible = true
+ })
+ })
+
+ camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height
+ camera.updateProjectionMatrix()
+ threeRenderer.setRenderTarget(null)
+ composer.render(delta)
+ }
+
+ loopId = requestAnimationFrame(loop)
+ }
+
+ loopId = requestAnimationFrame(loop)
+}
+
+function resize() {
+ const w = canvas.width
+ const h = canvas.height
+
+ threeRenderer.setSize(w, h)
+ composer.setSize(w, h)
+ customEffects.forEach(e => {
+ if (e.uniforms.get('resolution')) {
+ e.uniforms.get('resolution').value.set(w,h)
+ }
+ })
+
+ if (camera) {
+ camera.aspect = w / h
+ camera.updateProjectionMatrix()
+}
+}
+//wait until all packages are loaded
+Promise.resolve(load()).then(() => {
+
+ class threejsExtension {
+ getInfo() {
+ return {
+ id: "threejsExtension",
+ name: "Extra 3D",
+ color1: "#222222",
+ color2: "#222222",
+ color3: "#11cc99",
+ menuIconURI,
+ blockIconURI: menuIconURI,
+
+ blocks: [
+ {blockType: Scratch.BlockType.BUTTON, text: "Show Docs", func: "openDocs"},
+ {blockType: Scratch.BlockType.BUTTON, text: "Toggle Alerts", func: "alerts"},
+ ],
+ menus: {}
+ }}
+ openDocs(){
+ open("https://civ3ro.github.io/extensions/Documentation/")
+ }
+ alerts() {alerts = !alerts; alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!")}
+ }
+ Scratch.extensions.register(new threejsExtension())
+
+ class ThreeRenderer {
+ getInfo() {
+ return {
+ id: "threeRenderer",
+ name: "Three Renderer",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "setRendererRatio", blockType: Scratch.BlockType.COMMAND, text: "set Pixel Ratio to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"}}},
+ {opcode: "eulerOrder", blockType: Scratch.BlockType.COMMAND, text: "set euler order to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "YXZ"}}},
+ ],
+ menus: {}
+ }}
+
+ setRendererRatio(args) {
+ threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE)
+ }
+ eulerOrder(args) {
+ eulerOrder = args.VALUE
+ console.log("euler order set to", eulerOrder)
+ }
+
+ }
+ Scratch.extensions.register(new ThreeRenderer())
+
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
+ getInfo() {
+ return {
+ id: "threeScene",
+ name: "Three Scene",
+ color1: "#4638c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "newScene", blockType: Scratch.BlockType.COMMAND, text: "new Scene [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}}},
+
+ {opcode: "setSceneProperty", blockType: Scratch.BlockType.COMMAND, text: "set Scene [PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "sceneProperties", defaultValue: "background"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
+ "---",
+ {opcode: "getSceneObjects", blockType: Scratch.BlockType.REPORTER, text: "get Scene [THING]", arguments:{THING: {type: Scratch.ArgumentType.STRING, menu: "sceneThings"}}},
+ {opcode: "reset", blockType: Scratch.BlockType.COMMAND, text: "Reset Everything"}
+ ],
+ menus: {
+ sceneProperties: {acceptReporters: false, items: [
+ {text: "Background", value: "background"},{text: "Background Blurriness", value: "backgroundBlurriness"},{text: "Background Intensity", value: "backgroundIntensity"},{text: "Background Rotation", value: "backgroundRotation"},
+ {text: "Environment", value: "environment"},{text: "Environment Intensity", value: "environmentIntensity"},{text: "Environment Rotation", value: "environmentRotation"},{text: "Fog", value: "fog"},
+ ]},
+ sceneThings: {acceptReporters: false, items: ["Objects", "Materials", "Geometries","Lights","Scene Properties","Other assets"]},
+
+ }
+ }}
+
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME
+ scene.background = new THREE.Color("#222")
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {...this.scenes, {scene}};
+ resetor(0)
+ }
+
+ reset() {
+ resetor(1)
+ }
+
+ async setSceneProperty(args) {
+ const property = args.PROPERTY;
+ const value = getAsset(args.VALUE);
+
+ scene[property] = value;
+ }
+ getSceneObjects(args){
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse(obj => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ }
+ else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials))
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries))
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights))
+ else if (args.THING === "Scene Properties") {console.log(scene); return "check console"}
+ else if (args.THING === "Other assets") return JSON.stringify(assets)
+
+ return JSON.stringify(names); // if objects
+ }
+
+ }
+ Scratch.extensions.register(new ThreeScene())
+
+ class ThreeCameras {
+ getInfo() {
+ return {
+ id: "threeCameras",
+ name: "Three Cameras",
+ color1: "#38c59bff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "addCamera", blockType: Scratch.BlockType.COMMAND, text: "add camera [TYPE] [CAMERA] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "cameraTypes"}}},
+ {opcode: "setCamera", blockType: Scratch.BlockType.COMMAND, text: "set camera [PROPERTY] of [CAMERA] to [VALUE]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "0.1", exemptFromNormalization: true}}},
+ {opcode: "getCamera", blockType: Scratch.BlockType.REPORTER, text: "get camera [PROPERTY] of [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}}},
+ "---",
+ {opcode: "renderSceneCamera", blockType: Scratch.BlockType.COMMAND, text: "set rendering camera to [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}}},
+ "---",
+ {opcode: "cubeCamera", blockType: Scratch.BlockType.COMMAND, text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "cubeCamera"}, GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
+ "---",
+ {opcode: "renderTarget", blockType: Scratch.BlockType.COMMAND, text: "set a RenderTarget: [RT] for camera [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
+ {opcode: "sizeTarget", blockType: Scratch.BlockType.COMMAND, text: "set RenderTarget [RT] size to [W] [H]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, W: {type: Scratch.ArgumentType.NUMBER, defaultValue: 480}, H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 360},} },
+ {opcode: "getTarget", blockType: Scratch.BlockType.REPORTER, text: "get RenderTarget: [RT] texture", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
+ {opcode: "removeTarget", blockType: Scratch.BlockType.COMMAND, text: "remove RenderTarget: [RT]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
+ ],
+ menus: {
+ cameraTypes: {acceptReporters: false, items: [
+ {text: "Perspective", value: "PerspectiveCamera"},
+ ]},
+ cameraProperties: {acceptReporters: false, items: [
+ {text: "Near", value: "near"},{text: "Far", value: "far"},{text: "FOV", value: "fov"},{text: "Focus (nothing...)", value: "focus"},{text: "Zoom", value: "zoom"},
+ ]},
+ }
+ }}
+ addCamera(args) {
+ let v2 = new THREE.Vector2()
+ threeRenderer.getSize(v2)
+ const object = new THREE.PerspectiveCamera(90, v2.x / v2.y )
+ object.position.z = 3
+
+ createObject(args.CAMERA, object, args.GROUP)
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA)
+ object[args.PROPERTY] = args.VALUE
+ object.updateProjectionMatrix()
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA)
+ const value = JSON.stringify(object[args.PROPERTY])
+ return value
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA)
+ if (!object) return
+ camera = object
+ //reset composer, else it does not update.
+ composer.passes = []
+ passes = {}
+ customEffects = []
+ updateComposers()
+ }
+
+ cubeCamera(args) {
+ // Create cube render target
+ const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true } )
+ // Create cube camera
+ const cubeCamera = new THREE.CubeCamera( 0.1, 500, cubeRenderTarget )
+ createObject(args.CAMERA, cubeCamera, args.GROUP)
+
+ renderTargets[args.RT] = {target: cubeRenderTarget, camera: cubeCamera}
+ assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture
+ }
+
+ renderTarget(args) {
+ let object = getObject(args.CAMERA)
+ const renderTarget = new THREE.WebGLRenderTarget(
+ 360,
+ 360,
+ {
+ generateMipmaps: false
+ }
+ )
+
+ renderTargets[args.RT] = {target: renderTarget, camera: object}
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H)
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture
+ console.log(t, renderTargets[args.RT])
+ return `renderTargets/${t.uuid}`
+ }
+ removeTarget(args) {
+ delete(assets.renderTargets[renderTargets[args.RT].target.texture.uuid])
+ renderTargets[args.RT].target.dispose()
+ delete(renderTargets[args.RT])
+ }
+ }
+ Scratch.extensions.register(new ThreeCameras())
+
+ class ThreeObjects {
+ getInfo() {
+ return {
+ id: "threeObjects",
+ name: "Three Objects",
+ color1: "#38c567ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "addObject", blockType: Scratch.BlockType.COMMAND, text: "add object [OBJECT3D] [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},TYPE: {type: Scratch.ArgumentType.STRING, menu: "objectTypes"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {opcode: "cloneObject", blockType: Scratch.BlockType.COMMAND, text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myClone"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "setObject", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of object [OBJECT3D] to [NAME]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
+ {opcode: "getObject", blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of object [OBJECT3D]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}}},
+ {opcode: "objectE", blockType: Scratch.BlockType.BOOLEAN, text: "is there an object [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "removeObject", blockType: Scratch.BlockType.COMMAND, text: "remove object [OBJECT3D] from scene", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+
+ {blockType: Scratch.BlockType.LABEL, text: " ↳ Transforms"},
+ {opcode: "setObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
+ //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {opcode: "getObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of [OBJECT3D]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+
+ {blockType: Scratch.BlockType.LABEL, text: "↳ Materials"},
+ {opcode: "newMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "new material [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "materialTypes", defaultValue: "MeshStandardMaterial"}}},
+ {opcode: "materialE",extensions: ["colours_looks"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a material [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
+ {opcode: "removeMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "remove material [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
+ {opcode: "setMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [PROPERTY] of [NAME] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "materialProperties", defaultValue: "color"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
+ {opcode: "setBlending",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] blending to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "blendModes"}}},
+ {opcode: "setDepth",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] depth to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "depthModes"}}},
+
+ {blockType: Scratch.BlockType.LABEL, text: "↳ Geometries"},
+ {opcode: "newGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new geometry [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "geometryTypes", defaultValue: "BoxGeometry"}}},
+ {opcode: "geometryE",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a geometry [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
+ {opcode: "removeGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "remove geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
+ "---",
+ {opcode: "newGeo",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new empty geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
+ {opcode: "geoPoints",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] vertex points to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
+ {opcode: "geoUVs",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] UVs to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[UVs]"}}},
+ "---",
+ {opcode: "splines", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create spline [NAME] from curve [CURVE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}}},
+ {opcode: "splineModel", extensions: ["colours_operators"], blockType: Scratch.BlockType.COMMAND, text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, MODEL: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}, SPACING: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
+ "---",
+ {blockType: Scratch.BlockType.BUTTON, text: "Convert font to JSON", func: "openConv"},
+ {blockType: Scratch.BlockType.BUTTON, text: "Load JSON font file", func: "loadFont"},
+ {opcode: "text", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myText"}, TEXT: {type: Scratch.ArgumentType.STRING, defaultValue: "C-369"}, FONT: {type: Scratch.ArgumentType.STRING, menu: "fonts"}, S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, D: {type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1}, CS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 6}}},
+ ],
+ menus: {
+ objectVector3: {acceptReporters: false, items: [
+ {text: "Positon", value: "position"},{text: "Rotation", value: "rotation"},{text: "Scale", value: "scale"},{text: "Facing Direction (.up)", value: "up"}
+ ]},
+ objectProperties: {acceptReporters: false, items: [
+ {text: "Geometry", value: "geometry"},{text: "Material", value: "material"},{text: "Visible (true/false)", value: "visible"},
+ ]},
+ objectTypes: { acceptReporters: false, items: [
+ { text: "Mesh", value: "Mesh" }, { text: "Sprite", value: "Sprite" }, { text: "Points", value: "Points" }, { text: "Line", value: "Line" }, { text: "Group", value: "Group" }
+ ]},
+ XYZ: {acceptReporters: false, items: [{text: "X", value: "x"},{text: "Y", value: "y"},{text: "Z", value: "z"}]},
+ materialProperties: {acceptReporters: false, items: [
+ "|GENERAL| <-- not a property",
+ { text: "Color", value: "color" },
+ { text: "Map", value: "map" },
+ { text: "Opacity", value: "opacity" },
+ { text: "Transparent", value: "transparent" },
+ { text: "Alpha Map", value: "alphaMap" },
+ { text: "Alpha Test", value: "alphaTest" },
+ { text: "Depth Test", value: "depthTest" },
+ { text: "Depth Write", value: "depthWrite" },
+ { text: "Color Write", value: "colorWrite" },
+ { text: "Side", value: "side" },
+ { text: "Visible", value: "visible" },/*
+ { text: "Blending", value: "blending" },
+ { text: "Blend Src", value: "blendSrc" },
+ { text: "Blend Dst", value: "blendDst" },
+ { text: "Blend Equation", value: "blendEquation" },
+ { text: "Blend Src Alpha", value: "blendSrcAlpha" },
+ { text: "Blend Dst Alpha", value: "blendDstAlpha" },
+ { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
+ { text: "Blend Aplha", value: "blendAplha" },
+ { text: "Blend Color", value: "blendColor" },
+ { text: "Alpha Hash", value: "alphaHash" },
+ { text: "Premultiplied Alpha", value: "premultipliedAlpha" },
+
+ { text: "Tone Mapped", value: "toneMapped" },
+ { text: "Fog", value: "fog" },
+ { text: "Flat Shading", value: "flatShading" },
+
+ "|MESH Standard / Physical| <-- not a property",
+ { text: "Metalness", value: "metalness" },
+ { text: "Metalness Map", value: "metalnessMap" },
+ { text: "Roughness", value: "roughness" },
+ { text: "Reflectivity", value: "reflectivity" },
+ { text: "Roughness Map", value: "roughnessMap" },
+ { text: "Emissive", value: "emissive" },
+ { text: "Emissive Intensity", value: "emissiveIntensity" },
+ { text: "Emissive Map", value: "emissiveMap" },
+ { text: "Env Map", value: "envMap" },
+ { text: "Env Map Intensity", value: "envMapIntensity" },
+ { text: "Env Map Rotation", value: "envMapRotation" },
+ { text: "Ior", value: "ior" },
+ { text: "Refraction Ratio", value: "refractionRatio" },
+ { text: "Clearcoat", value: "clearcoat" },
+ { text: "Clearcoat Map", value: "clearcoatMap" },
+ { text: "Clearcoat Roughness", value: "clearcoatRoughness" },
+ { text: "Clearcoat Roughness Map", value: "clearcoatRoughnessMap" },
+ { text: "Dispersion", value: "dispersion" },
+ { text: "Sheen", value: "sheen" },
+ { text: "Sheen Color", value: "sheenColor" },
+ { text: "Sheen Color Map", value: "sheenColorMap" },
+ { text: "Sheen Roughness", value: "sheenRoughness" },
+ { text: "Sheen Roughness Map", value: "sheenRoughnessMap" },
+ { text: "Specular Color", value: "specularColor" },
+ { text: "Specular Color Map", value: "specularColorMap" },
+ { text: "Specular Intensity", value: "specularIntensity" },
+ { text: "Specular Intensity Map", value: "specularIntensityMap" },
+ { text: "Transmission", value: "transmission" },
+ { text: "Transmission Map", value: "transmissionMap" },
+ { text: "Thickness", value: "thickness" },
+ { text: "Thickness Map", value: "thicknessMap" },
+ { text: "Anisotropy", value: "anisotropy" },
+ { text: "Anisotropy Map", value: "anisotropyMap" },
+ { text: "Anisotropy Rotation", value: "anisotropyRotation" },
+ { text: "Attenuation Distance", value: "attenuationDistance" },
+ { text: "Attenuation Color", value: "attenuationColor" },
+ { text: "Thickness", value: "thickness" },
+ { text: "Iridescence", value: "iridescence" },
+ { text: "Iridescence Ior", value: "iridescenceIOR" },
+ { text: "Iridescence Map", value: "iridescenceMap" },
+ { text: "Iridescence Thickness Range", value: "iridescenceThicknessRange" },
+
+ "|MESH Displacement / Normal / Bump| <-- not a property",
+ { text: "Displacement Map", value: "displacementMap" },
+ { text: "Displacement Scale", value: "displacementScale" },
+ { text: "Displacement Bias", value: "displacementBias" },
+ { text: "Bump Map", value: "bumpMap" },
+ { text: "Bump Scale", value: "bumpScale" },
+ { text: "Normal Map Type", value: "normalMapType" },
+
+ "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
+ { text: "Shininess", value: "shininess" },
+
+ { text: "Wireframe", value: "wireframe" },
+ { text: "Wireframe Linewidth", value: "wireframeLinewidth" },
+ { text: "Wireframe Linecap", value: "wireframeLinecap" },
+ { text: "Wireframe Linejoin", value: "wireframeLinejoin" },
+
+ "|POINTS| <-- not a property",
+ { text: "Size", value: "size" },
+ { text: "Size Attenuation", value: "sizeAttenuation" },
+
+ "|LINES| <-- not a property",
+ { text: "Scale", value: "scale" },
+ { text: "Dash Size", value: "dashSize" },
+ { text: "Gap Size", value: "gapSize" },
+
+ "|SPRITES| <-- not a property",
+ { text: "Rotation", value: "rotation" }
+]},
+ blendModes: {acceptReporters: false, items: [
+ { text: "No Blending", value: "NoBlending" },{ text: "Normal Blending", value: "NormalBlending" },{ text: "Additive Blending", value: "AdditiveBlending" },{ text: "Subtractive Blending", value: "SubtractiveBlending" },{ text: "Multiply Blending", value: "MultiplyBlending" },{ text: "Custom Blending", value: "CustomBlending" }
+ ]},
+ depthModes: {acceptReporters: false, items: [
+ { text: "Never Depth", value: "NeverDepth" },{ text: "Always Depth", value: "AlwaysDepth" },{ text: "Equal Depth", value: "EqualDepth" },{ text: "Less Depth", value: "LessDepth" },{ text: "Less Equal Depth", value: "LessEqualDepth" },{ text: "Greater Equal Depth", value: "GreaterEqualDepth" },{ text: "Greater Depth", value: "GreaterDepth" },{ text: "Not Equal Depth", value: "NotEqualDepth" }
+ ]},
+ materialTypes:{acceptReporters: false, items: [
+ {text:"Mesh Basic Material",value:"MeshBasicMaterial"},{text:"Mesh Standard Material",value:"MeshStandardMaterial"},{text:"Mesh Physical Material",value:"MeshPhysicalMaterial"},{text:"Mesh Lambert Material",value:"MeshLambertMaterial"},{text:"Mesh Phong Material",value:"MeshPhongMaterial"},{text:"Mesh Depth Material",value:"MeshDepthMaterial"},{text:"Mesh Normal Material",value:"MeshNormalMaterial"},{text:"Mesh Matcap Material",value:"MeshMatcapMaterial"},{text:"Mesh Toon Material",value:"MeshToonMaterial"},{text:"Line Basic Material",value:"LineBasicMaterial"},{text:"Line Dashed Material",value:"LineDashedMaterial"},{text:"Points Material",value:"PointsMaterial"},{text:"Sprite Material",value:"SpriteMaterial"},{text:"Shadow Material",value:"ShadowMaterial"}
+ ]},
+ textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
+ textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
+ geometryTypes: {acceptReporters: false, items: [
+ {text: "Box Geometry", value: "BoxGeometry"},{text: "Sphere Geometry", value: "SphereGeometry"},{text: "Cylinder Geometry", value: "CylinderGeometry"},{text: "Plane Geometry", value: "PlaneGeometry"},{text: "Circle Geometry", value: "CircleGeometry"},{text: "Torus Geometry", value: "TorusGeometry"},{text: "Torus Knot Geometry", value: "TorusKnotGeometry"},
+ ]},
+ modelsList: {acceptReporters: false, items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
+ if (models.length < 1) return [["Load a model! (GLB Loader category)"]]
+
+ // @ts-ignore
+ return models.map( m => [m.name] )
+ }},
+ fonts: {acceptReporters: false, items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.json'))
+ if (models.length < 1) return [["Load a font!"]]
+
+ // @ts-ignore
+ return models.map( m => [m.name] )
+ }},
+
+ }
+ }}
+
+ addObject(args) {
+ const object = new THREE[args.TYPE]();
+
+ object.castShadow = true
+ object.receiveShadow = true
+
+ createObject(args.OBJECT3D, object, args.GROUP)
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D)
+ const clone = object.clone(true)
+ clone.name
+ createObject(args.NAME, clone, args.GROUP)
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
+
+ function degToRad(deg) {
+ return deg * Math.PI / 180;
+ }
+
+
+ if (object.rigidBody) {
+ const x = values[0]
+ const y = values[1]
+ const z = values[2]
+ if (args.PROPERTY === "rotation") {
+ const euler = new THREE.Euler(
+ degToRad(x),
+ degToRad(y),
+ degToRad(z),
+ 'YXZ'
+ )
+ const quaternion = new THREE.Quaternion()
+ quaternion.setFromEuler(euler)
+
+ object.rigidBody.setRotation({
+ x: quaternion.x,
+ y: quaternion.y,
+ z: quaternion.z,
+ w: quaternion.w
+ });
+ } else if (args.PROPERTY === "position") {
+ object.rigidBody.setTranslation({ x: x, y: y, z: z }, true)
+ }
+ return
+ }
+
+ if (object.isCamera == true && controls) {
+
+ }
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.set(0,0,0)
+ }
+ if (object.isDirectionalLight == true) {object.pos = new THREE.Vector3(...values); console.log(true, values, object.pos); return}
+ object[args.PROPERTY].set(...values);
+
+ if (object.type == "CubeCamera") object.updateCoordinateSystem()
+ }
+ /*
+ changeObjectV3(args) {
+ getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.x += values[0]
+ object.rotation.y += values[1]
+ object.rotation.z += values[2]
+ }
+ else {
+ object[args.PROPERTY].add(...values);
+ }
+ }
+ changeObjectXV3(args) {
+ getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "rotation") value = value * Math.PI / 180
+
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D)
+ if (!object) return
+ let values = vector3ToString(object[args.PROPERTY])
+ if (args.PROPERTY === "rotation") {
+ const toDeg = Math.PI/180
+ values = [values[0]/toDeg,values[1]/toDeg,values[2]/toDeg,]
+ }
+
+ return JSON.stringify(values)
+ }
+ setObject(args){
+ let object = getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "material") {const mat = materials[args.NAME]; if (mat) value = mat; else value = undefined}
+ else if (args.PROPERTY === "geometry") {const geo = geometries[args.NAME]; if (geo) value = geo; else value = undefined}
+ else value = !!value
+
+ if (value == undefined) return //invalid geo/mat
+ object[args.PROPERTY] = value
+ }
+ getObject(args){
+ let object = getObject(args.OBJECT3D)
+ if (!object) return
+ let value
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D)
+ }
+ objectE(args) {
+ return scene.children.map(o => o.name).includes(args.NAME)
+ }
+
+//defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert ("material already exists! will replace...")
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
+
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof(args.VALUE) == "string" && args.VALUE.at(0) == "|") return
+ const mat = materials[args.NAME]
+
+ let value = args.VALUE
+
+ if (args.VALUE == "false") value = false
+
+ if (args.PROPERTY == "side") {value = (args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide)}
+ else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE))
+ else value = getAsset(value)
+
+
+ console.log("o:", args.VALUE, typeof(args.VALUE))
+ console.log("r:", value, typeof(value))
+
+ mat[args.PROPERTY] = await (value) //await incase its a texture
+ mat.needsUpdate = true
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME]
+ mat.blending = THREE[args.VALUE]
+ mat.premultipliedAlpha = true
+ mat.needsUpdate = true
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME]
+ mat.depthFunc = THREE[args.VALUE]
+ mat.needsUpdate = true
+ }
+ removeMaterial(args){
+ const mat = materials[args.NAME]
+ mat.dispose()
+ delete(materials[args.NAME])
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false
+ }
+
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert ("geometry already exists! will replace...")
+ const geo = new THREE[args.TYPE]()
+ geo.name = args.NAME
+
+ geometries[args.NAME] = geo
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME]
+ geo[args.PROPERTY] = (args.VALUE)
+
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args){
+ const geo = geometries[args.NAME]
+ geo.dispose()
+ delete(geometries[args.NAME])
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false
+ }
+
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry()
+ geometry.name = args.NAME
+ geometries[args.NAME] = geometry
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME]
+ const positions = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v3 of each vertex of each triangle
+
+ geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
+ geometry.computeVertexNormals()
+
+ geometry.needsUpdate = true
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME]
+ const UVs = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v2 of each UV of each triangle
+
+ geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(UVs), 2))
+ geometry.needsUpdate = true
+ }
+
+ splines(args) {
+ const geometry = new THREE.TubeGeometry(getAsset(args.CURVE))
+ geometry.name = args.NAME
+
+ geometries[args.NAME] = geometry
+ }
+
+ async splineModel(args) {
+ const model = await getModel(args.MODEL, args.NAME)
+ if (!model) return console.warn("Model not found:", args.MODEL)
+
+ const curve = getAsset(args.CURVE)
+ const spacing = parseFloat(args.SPACING) || 1
+ const curveLength = curve.getLength()
+ const divisions = Math.floor(curveLength / spacing)
+
+ const geomList = []
+ const matList = []
+
+ for (let i = 0; i <= divisions; i++) {
+ const t = i / divisions
+ const pos = curve.getPointAt(t)
+ const tangent = curve.getTangentAt(t)
+
+ const temp = model.clone(true)
+ temp.position.copy(pos)
+
+ const up = new THREE.Vector3(0, 0, 1)
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize())
+ temp.quaternion.copy(quat)
+
+ temp.updateMatrixWorld(true)
+
+ temp.traverse(child => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone()
+ geom.applyMatrix4(child.matrixWorld)
+ geomList.push(geom)
+ matList.push(child.material) //.clone() ?
+ }
+ })
+ }
+
+ const validGeoms = geomList.filter(g => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position
+ if (!ok) console.warn("geometry skipped:", g)
+ return ok
+ })
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true)
+ merged.computeBoundingBox()
+ merged.computeBoundingSphere()
+
+ merged.name = args.NAME
+ geometries[args.NAME] = merged
+ matList.name = args.NAME
+ materials[args.NAME] = matList
+ }
+
+ async text(args) {
+ const fontFile = runtime.getTargetForStage().getSounds().find(c => c.name === args.FONT)
+ if (!fontFile) return
+
+ const json = new TextDecoder().decode(fontFile.asset.data.buffer)
+ const fontData = JSON.parse(json)
+
+ const font = fontLoad.parse(fontData)
+
+ const params = {font: font, size: JSON.parse(args.S), height: JSON.parse(args.D), curveSegments: JSON.parse(args.CS), bevelEnabled: false}
+ const geometry = new TextGeometry.TextGeometry(args.TEXT, params)
+ geometry.computeVertexNormals()
+ geometry.center() // optional, recenters the text
+
+
+ geometry.name = args.NAME
+
+ geometries[args.NAME] = geometry
+ }
+
+ async loadFont() {
+ openFileExplorer(".json").then(files => {
+ const file = files[0]
+ const reader = new FileReader()
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result
+
+ // From lily's assets
+ // // Thank you PenguinMod for providing this code.
+
+ const targetId = runtime.getTargetForStage().id //util.target.id not working!
+ const assetName = Cast.toString(file.name)
+
+ const buffer = arrayBuffer
+
+ const storage = runtime.storage
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ )
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ )
+ alert("Font loaded successfully!")
+ } catch (e) {
+ console.error(e)
+ alert("Error loading font.")
+ }
+
+ // End of PenguinMod
+ }
+
+ reader.readAsArrayBuffer(file);
+ })
+ }
+ openConv() {{open("https://gero3.github.io/facetype.js/")}}
+
+ }
+ Scratch.extensions.register(new ThreeObjects())
+
+ class ThreeLights {
+ getInfo() {
+ return {
+ id: "threeLights",
+ name: "Three Lights",
+ color1: "#c7a22aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "addLight", blockType: Scratch.BlockType.COMMAND, text: "add light [NAME] type [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "lightTypes"}}},
+ {opcode: "setLight", blockType: Scratch.BlockType.COMMAND, text: "set light [NAME][PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lightProperties", defaultValue: "intensity"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1", exemptFromNormalization: true}}},
+ ],
+ menus: {
+ lightTypes: {acceptReporters: false, items: [
+ {text: "Ambient Light", value: "AmbientLight"},{text: "Directional Light", value: "DirectionalLight"},{text: "Point Light", value: "PointLight"},{text: "Hemisphere Light", value: "HemisphereLight"},{text: "Spot Light", value: "SpotLight"},
+ ]},
+ lightProperties: {acceptReporters: false, items: [
+ {text: "Color", value: "color"},{text: "Intensity", value: "intensity"},{text: "Cast Shadow?", value: "castShadow"},
+ {text: "Ground Color (HemisphereLight)", value: "groundColor"},
+ {text: "Map (SpotLight)", value: "map"},{text: "Distance (SpotLight)", value: "distance"},{text: "Decay (SpotLight)", value: "decay"},{text: "Penumbra (SpotLight)", value: "penumbra"},{text: "Angle/Size (SpotLight)", value: "angle"},{text: "Power (SpotLight)", value: "power"},
+ {text: "Target Position (Directional/SpotLight)", value: "target"},
+ ]},
+ }
+ }}
+
+ addLight(args) {
+ const light = new THREE[args.TYPE](0xffffff, 1)
+
+ createObject(args.NAME, light, args.GROUP)
+ lights[args.NAME] = light
+ if (light.type === "AmbientLight" || "HemisphereLight") return
+
+ light.castShadow = true
+ if (light.type === "PointLight") return
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0)
+ scene.add(light.target)
+
+ light.pos = new THREE.Vector3(0,0,0)
+
+ light.shadow.mapSize.width = 4096
+ light.shadow.mapSize.height = 2048
+
+ if (light.type === "SpotLight") {
+ light.decay = 0
+ light.shadow.camera.near = 500;
+ light.shadow.camera.far = 4000;
+ light.shadow.camera.fov = 30;
+ }
+ light.shadow.needsUpdate = true
+ light.needsUpdate = true
+ }
+
+ setLight(args) {
+ const light = lights[args.NAME]
+ if (!args.PROPERTY) return
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)) //vector3
+ light.target.updateMatrixWorld();
+ }
+ else {
+ light[args.PROPERTY] = getAsset(args.VALUE)
+ }
+ light.needsUpdate = true
+
+ if (light.type === "AmbientLight" || "HemisphereLight") return
+
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true
+ }
+
+ }
+ Scratch.extensions.register(new ThreeLights())
+
+ class ThreeUtilities {
+ getInfo() {
+ return {
+ id: "threeUtility",
+ name: "Three Utilities",
+ color1: "#3875c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "newVector2", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}}},
+ {opcode: "newVector3", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y] [Z]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}, Z: {type: Scratch.ArgumentType.NUMBER}}},
+ "---",
+ {opcode: "operateV3", blockType: Scratch.BlockType.REPORTER, text: "do [V3] [O] [V32]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, O: {type: Scratch.ArgumentType.STRING, menu: "operators"}, V32: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
+ {opcode: "moveVector3", blockType: Scratch.BlockType.REPORTER, text: "move [S] steps in vector [V3] in direction [D3]", arguments: {S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
+ {opcode: "directionTo", blockType: Scratch.BlockType.REPORTER, text: "direction from [V3] to [T3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, T3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ "---",
+ {opcode: "newColor", blockType: Scratch.BlockType.REPORTER, text: "New Color [HEX]", arguments: {HEX: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff"}}},
+ {opcode: "newFog", blockType: Scratch.BlockType.REPORTER, text: "New Fog [COLOR] [NEAR] [FAR]", arguments: {COLOR: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff", exemptFromNormalization: true}, NEAR: {type: Scratch.ArgumentType.NUMBER}, FAR: {type: Scratch.ArgumentType.NUMBER, defaultValue: 10}}},
+ {opcode: "newTexture", blockType: Scratch.BlockType.REPORTER, text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
+ {opcode: "newCubeTexture", blockType: Scratch.BlockType.REPORTER, text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]", arguments: {"COSTUMEX0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEX1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ1": {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
+ {opcode: "newEquirectangularTexture", blockType: Scratch.BlockType.REPORTER, text: "New Equirectangular Texture [COSTUME] [MODE]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"}}},
+ "---",
+ {opcode: "curve", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]", arguments: {TYPE: {type: Scratch.ArgumentType.STRING, menu: "curveTypes"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]"}, CLOSED: {type: Scratch.ArgumentType.STRING, defaultValue: "true"}}},
+ "---",
+ {opcode:"mouseDown",extensions: ["colours_sensing"], blockType: Scratch.BlockType.BOOLEAN, text: "mouse [BUTTON] [action]?", arguments: {BUTTON: {type: Scratch.ArgumentType.STRING, menu: "mouseButtons"},action: {type: Scratch.ArgumentType.STRING, menu: "mouseAction"}}},
+ {opcode: "mousePos",extensions: ["colours_sensing"], blockType: Scratch.BlockType.REPORTER, text: "mouse position", arguments: {}},
+ "---",
+ {opcode: "getItem",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "get item [ITEM] of [ARRAY]", arguments: {ITEM: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}, ARRAY: {type: Scratch.ArgumentType.STRING, defaultValue: `["myObject", "myLight"]`}}},
+ {blockType: Scratch.BlockType.LABEL, text: "↳ Raycasting"},
+ {opcode: "raycast", blockType: Scratch.BlockType.COMMAND, text: "Raycast from [V3] in direction [D3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,1]"}}},
+ {opcode: "getRaycast", blockType: Scratch.BlockType.REPORTER, text: "get raycast [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "raycastProperties"}}},
+
+ ],
+ menus: {
+ materialProperties: {acceptReporters: false, items: [
+ {text: "Color", value: "color"},{text: "Map (texture)", value: "map"},{text: "Alpha Map (texture)", value: "alphaMap"},{text: "Alpha Test (0-1)", value: "alphaTest"},{text: "Side (front/back/double)", value: "side"},{text: "Bump Map (texture)", value: "bumpMap"},{text: "Bump Scale", value: "bumpScale"},
+ ]},
+ textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
+ textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
+ raycastProperties: {acceptReporters: false, items: [
+ {text: "Intersected Object Names", value: "name"},{text: "Number of Objects", value: "number"},{text: "Intersected Objects distances", value: "distance"},
+ ]},
+ mouseButtons: {acceptReporters: false, items: ["left","middle","right"]},
+ mouseAction: {acceptReporters: false, items: ["Down","Clicked"]},
+ curveTypes: {acceptReporters: false, items: ["CatmullRomCurve3"]},
+ operators: {acceptReporters: false, items: [
+ "+","-","*","/","=","max","min","dot","cross","distance to","angle to","apply euler",
+ ]}
+ }
+ }}
+ mouseDown(args) {
+ if (args.action === "Down") return isMouseDown[args.BUTTON]
+ if (args.action === "Clicked") {
+ if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false
+ else prevMouse[args.BUTTON] = true; return true
+ }
+ }
+ mousePos(event) {
+ return JSON.stringify(mouseNDC)
+ }
+ newVector3(args) {
+ return JSON.stringify([args.X, args.Y, args.Z])
+ }
+ operateV3(args){
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3))
+ const v32 = new THREE.Vector3(...JSON.parse(args.V32))
+
+ let r
+ if (args.O == "+") r = v3.add(v32)
+ else if (args.O == "-") r = v3.sub(v32)
+ else if (args.O == "*") r = v3.multiply(v32)
+ else if (args.O == "/") r = v3.divide(v32)
+ else if (args.O == "=") r = v3.equals(v32)
+ else if (args.O == "max") r = v3.max(v32)
+ else if (args.O == "min") r = v3.min(v32)
+ else if (args.O == "dot") r = v3.dot(v32)
+ else if (args.O == "cross") r = v3.cross(v32)
+ else if (args.O == "distance to") r = v3.distanceTo(v32)
+ else if (args.O == "angle to") r = v3.angleTo(v32)
+ else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder))
+
+ if (typeof(r) == "object") return JSON.stringify([r.x, r.y, r.z])
+ else return JSON.stringify(r)
+ }
+
+ newVector2(args) {
+ return JSON.stringify([args.X, args.Y])
+ }
+
+ moveVector3(args) {
+ const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
+ const steps = Number(args.S);
+
+ const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
+
+ const yaw = THREE.MathUtils.degToRad(yawInputDeg);
+ const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
+ const roll = THREE.MathUtils.degToRad(rollInputDeg);
+
+ const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
+
+ const forwardVector = new THREE.Vector3(0, 0, -1);
+ const direction = forwardVector.applyEuler(euler).normalize();
+
+ const newPos = currentPos.add(direction.multiplyScalar(steps));
+ return JSON.stringify([newPos.x, newPos.y, newPos.z]);
+ }
+
+ directionTo(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3))
+ const toV3 = new THREE.Vector3(...JSON.parse(args.T3))
+
+ const direction = toV3.clone().sub(v3).normalize();
+ // Pitch (X)
+ const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x*direction.x + direction.z*direction.z));
+ // Yaw (Y)
+ const yaw = Math.atan2(direction.x, direction.z);
+
+ // Roll always 0
+ return JSON.stringify([180+THREE.MathUtils.radToDeg(pitch),THREE.MathUtils.radToDeg(yaw),0])
+ }
+
+ newColor(args) {
+ const color = new THREE.Color(args.HEX)
+ const uuid = crypto.randomUUID()
+ assets.colors[uuid] = color
+ return `colors/${uuid}`
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR)
+ const uuid = crypto.randomUUID()
+ assets.fogs[uuid] = fog
+ return `fogs/${uuid}`
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME)
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
+ assets.textures[texture.uuid] = texture
+ return `textures/${texture.uuid}`
+ }
+ async newCubeTexture(args) {
+ const uris = [encodeCostume(args.COSTUMEX0),encodeCostume(args.COSTUMEX1), encodeCostume(args.COSTUMEY0),encodeCostume(args.COSTUMEY1), encodeCostume(args.COSTUMEZ0),encodeCostume(args.COSTUMEZ1)]
+ const normalized = await Promise.all(uris.map(uri => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
+
+ texture.name = "CubeTexture" + args.COSTUMEX0;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
+ assets.textures[texture.uuid] = texture
+ return `textures/${texture.uuid}`
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME)
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME
+ texture.mapping = THREE.EquirectangularReflectionMapping
+
+ setTexutre(texture, args.MODE)
+ assets.textures[texture.uuid] = texture
+ return `textures/${texture.uuid}`
+ }
+
+ curve(args) {
+ function parsePoints(input) {
+ // Match all [x,y,z] groups
+ const matches = input.match(/\[([^\]]+)\]/g)
+ if (!matches) return []
+
+ return matches.map(str => {
+ const nums = str
+ .replace(/[\[\]\s]/g, '')
+ .split(',')
+ .map(Number)
+ return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0)
+ })
+ }
+ const points = parsePoints(args.POINTS)
+ const curve = new THREE[args.TYPE](points)
+ curve.closed = JSON.parse(args.CLOSED)
+
+ const uuid = crypto.randomUUID()
+ assets.curves[uuid] = curve
+ return `curves/${uuid}`
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY)
+ const item = items[args.ITEM - 1]
+ if (!item) return "0"
+ return item
+ }
+
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3))
+ // rotation is in degrees => convert to radians first
+ const rot = JSON.parse(args.D3).map(v => v * Math.PI / 180)
+
+ const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder)
+ const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize()
+
+ const raycaster = new THREE.Raycaster()
+ //const camera = getObject(args.CAMERA)
+ raycaster.set( origin, direction );
+
+ const intersects = raycaster.intersectObjects( scene.children, true )
+
+ raycastResult = intersects
+ }
+ getRaycast(args) {
+ if (args.PROPERTY === "number") return raycastResult.length
+ if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map(i => i.distance))
+ return JSON.stringify(raycastResult.map(i => i.object[args.PROPERTY]))
+ }
+
+ }
+ Scratch.extensions.register(new ThreeUtilities())
+
+ class ThreeGLB {
+ getInfo() {
+ return {
+ id: "threeGLB",
+ name: "Three GLB Loader",
+ color1: "#c53838ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {blockType: Scratch.BlockType.BUTTON, text: "Load GLB File", func: "loadModelFile"},
+ {opcode: "addModel", blockType: Scratch.BlockType.COMMAND, text: "add [ITEM] as [NAME] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},ITEM: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}}},
+ {opcode: "getModel", blockType: Scratch.BlockType.REPORTER, text: "get object [PROPERTY] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "modelProperties"}}},
+ {opcode: "playAnimation", blockType: Scratch.BlockType.COMMAND, text: "play animation [ANAME] of [NAME], [TIMES] times", arguments: {TIMES: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
+ {opcode: "pauseAnimation", blockType: Scratch.BlockType.COMMAND, text: "set [TOGGLE] animation [ANAME] of [NAME]", arguments: {TOGGLE: {type: Scratch.ArgumentType.NUMBER, menu: "pauseUn"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
+ {opcode: "stopAnimation", blockType: Scratch.BlockType.COMMAND, text: "stop animation [ANAME] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
+
+ ],
+ menus: {
+ modelProperties: {acceptReporters: false, items: [
+ {text: "Animations", value: "animations"},
+ ]},
+ pauseUn: {acceptReporters: true, items: [{text: "Pause", value: "true"},{text: "Unpasue", value: "false"},]},
+ modelsList: {acceptReporters: false, items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
+ if (models.length < 1) return [["Load a model!"]]
+
+ // @ts-ignore
+ return models.map( m => [m.name] )
+ }},
+ }
+ }}
+
+ async loadModelFile() {
+
+ openFileExplorer(".glb").then(files => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ { // From lily's assets
+
+ // Thank you PenguinMod for providing this code.
+ {
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ //const res = await Scratch.fetch(args.URL);
+ //const buffer = await res.arrayBuffer();
+ const buffer = arrayBuffer
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Model loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading model.");
+ }
+ }
+ // End of PenguinMod
+ }
+ };
+
+ reader.readAsArrayBuffer(file);
+ })
+
+ }
+ async addModel(args) {
+ const group = await getModel(args.ITEM, args.NAME)
+
+ createObject(args.NAME, group, args.GROUP)
+ }
+ getModel(args){
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString()
+ }
+
+ playAnimation(args) {
+ const model = models[args.NAME]
+ if (!model) {console.log("no model!"); return}
+
+ const action = model.actions[args.ANAME] //clones of models dont have a stored actions!
+ if (!action) {
+ console.log("no action!")
+ return
+ }
+
+ args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity)
+
+ action.reset()
+ .play()
+ }
+ stopAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.stop();
+ }
+ pauseAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.paused = args.TOGGLE
+ }
+
+ }
+ Scratch.extensions.register(new ThreeGLB())
+
+ class ThreeAddons {
+ getInfo() {
+ return {
+ id: "threeAddons",
+ name: "Three Addons",
+ color1: "#c538a2ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {blockType: Scratch.BlockType.LABEL, text: "Orbit Control"},
+ {opcode: "OrbitControl", blockType: Scratch.BlockType.COMMAND, text: "set addon Orbit Control [STATE]", arguments: {STATE: {type: Scratch.ArgumentType.STRING, menu: "onoff"},}},
+
+ {blockType: Scratch.BlockType.LABEL, text: "Post Processing"},
+ {opcode: "resetComposer", blockType: Scratch.BlockType.COMMAND, text: "reset composer"},
+ {opcode: "bloom", blockType: Scratch.BlockType.COMMAND, text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},I: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, T:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
+ {opcode: "godRays", blockType: Scratch.BlockType.COMMAND, text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}, DEC:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.95}, DENS:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},EXP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1},WEI:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.4},RES:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},SAMP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 64},}},
+ {opcode: "dots", blockType: Scratch.BlockType.COMMAND, text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, A: {type: Scratch.ArgumentType.ANGLE, defaultValue: 0},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
+ {opcode: "depth", blockType: Scratch.BlockType.COMMAND, text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]", arguments: {FD: {type: Scratch.ArgumentType.NUMBER, defaultValue: (3)},FL: {type: Scratch.ArgumentType.NUMBER, defaultValue: (0.001)},BS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 4},H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 240},OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}}},
+ "---",
+ {opcode: "custom", blockType: Scratch.BlockType.COMMAND, text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myShader"}, FRA: {type: Scratch.ArgumentType.STRING}, VER: {type: Scratch.ArgumentType.STRING}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}, OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
+ ],
+ menus: {
+ onoff: {acceptReporters: true, items: [{text: "enabled", value: "1"},{text: "disabled", value: "0"},]},
+ blendModes: {acceptReporters: false, items: [
+ "SKIP","SET","ADD","ALPHA","AVERAGE","COLOR","COLOR_BURN","COLOR_DODGE",
+ "DARKEN","DIFFERENCE","DIVIDE","DST","EXCLUSION","HARD_LIGHT","HARD_MIX",
+ "HUE","INVERT","INVERT_RGB","LIGHTEN","LINEAR_BURN","LINEAR_DODGE",
+ "LINEAR_LIGHT","LUMINOSITY","MULTIPLY","NEGATION","NORMAL","OVERLAY",
+ "PIN_LIGHT","REFLECT","SCREEN","SRC","SATURATION","SOFT_LIGHT","SUBTRACT",
+ "VIVID_LIGHT"
+ ]},
+ }
+ }}
+
+ OrbitControl(args) {
+ if (controls) controls.dispose()
+
+ console.log("creating...", OrbitControls)
+ controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
+ controls.enableDamping = true
+
+ controls.enabled = !!args.STATE
+ console.log(controls)
+ }
+
+ resetComposer() {
+ composer.passes = []
+ passes = {}
+ customEffects = []
+ updateComposers()
+ }
+
+ bloom(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ bloomEffect.blendMode.opacity.value = args.OP
+
+ const pass = new EffectPass(camera, bloomEffect)
+
+ composer.addPass(pass)
+ }
+
+ godRays(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ let object = getObject(args.NAME)
+ const sun = object
+
+ const godRays = new GodRaysEffect(camera, sun, {
+ resolutionScale: args.RES,
+ density: args.DENS, // ray density
+ decay: args.DEC, // fade out
+ weight: args.WEI, // brightness of rays
+ exposure: args.EXP,
+ samples: args.SAMP,
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ godRays.blendMode.opacity.value = args.OP
+ const pass = new EffectPass(camera, godRays)
+ composer.addPass(pass)
+ }
+
+ dots(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ dot.blendMode.opacity.value = args.OP
+ const pass = new EffectPass(camera, dot)
+ composer.addPass(pass)
+ }
+
+ depth(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ dofEffect.blendMode.opacity.value = args.OP
+
+ const dofPass = new EffectPass(camera, dofEffect)
+ composer.addPass(dofPass)
+ }
+
+ async custom(args) {
+ function cleanGLSL(glslCode) {
+ //delete multilines comments
+ let cleanedCode = glslCode.replace(/\/\*[\s\S]*?\*\//g, ' ')
+ .replace(/ /g, '\n')
+ .replace(/\/\/.*$/gm, ' ')
+ .replace(/; /g, ';\n')
+
+ return cleanedCode;
+ }
+
+ let fs = cleanGLSL(`
+ ${args.FRA}
+ `)
+ if (!args.FRA.trim()) {fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`}
+ const vs = cleanGLSL(`
+ ${args.VER}
+ `)
+ console.log(fs)
+ console.log(vs)
+
+ const effect = new Effect(
+ "Custom",
+ fs,
+ {
+ blendFunction: BlendFunction[args.BLEND],
+ vertexShader: vs,
+ uniforms: new Map([ //uniforms usually in shaders... open to more!
+ ['time', new THREE.Uniform(0.0)],
+ ['resolution', new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height))]
+ ]),
+ defines: new Map([['USE_TIME', '1'], ['USE_VERTEX_TRANSFORM', '']]),
+ }
+ );
+
+ effect.blendMode.opacity.value = args.OP
+
+ const pass = new EffectPass(camera, effect);
+ composer.addPass(pass);
+
+ customEffects.push(effect);
+ }
+
+ }
+ Scratch.extensions.register(new ThreeAddons())
+
+ class RapierPhysics {
+ getInfo() {
+ return {
+ id: "rapierPhysics",
+ name: "RAPIER Physics",
+ color1: "#222222",
+ color2: "#203024ff",
+ color3: "#78f07eff",
+ blocks: [
+ {opcode: "createWorld", blockType: Scratch.BlockType.COMMAND, text: "create world | gravity:[G]", arguments: {G: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,-9.81,0]"}}},
+ {opcode: "getWorld", blockType: Scratch.BlockType.REPORTER, text: "get world [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "wProp"}}},
+ "---",
+ {opcode: "objectPhysics", blockType: Scratch.BlockType.COMMAND, text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]", arguments: {state2: {type: Scratch.ArgumentType.STRING, menu: "state2"},state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"}, type: {type: Scratch.ArgumentType.STRING, menu: "objectTypes", defaultValue: "dynamic"}, collider: {type: Scratch.ArgumentType.STRING, menu: "colliderTypes", defaultValue: "cuboid"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},mass: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},density: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},friction: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0.5"}}},
+ "---",
+ {blockType: Scratch.BlockType.LABEL, text: "- RigidBody"},
+ {opcode: "setRB", blockType: Scratch.BlockType.COMMAND, text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodySets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {opcode: "getRB", blockType: Scratch.BlockType.REPORTER, text: "get rigidbody [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodyProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "lockObjectAxis", blockType: Scratch.BlockType.COMMAND, text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]", arguments: {OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lockAxes"}, X: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Y: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Z: {type: Scratch.ArgumentType.STRING, menu: "tf"}}},
+ "---",
+ {opcode: "addForce", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,10,0]"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "forces", defaultValue: "addForce"},SPACE: {type: Scratch.ArgumentType.STRING, menu: "spaces", defaultValue: "world"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {opcode: "resetForces", blockType: Scratch.BlockType.COMMAND, text: "reset [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "resetF", defaultValue: "resetForces"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "enableCCD", blockType: Scratch.BlockType.COMMAND, text: "enable Continuous Collision Detection for [OBJECT] [state]", arguments: {state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "oPropS", defaultValue: "physics"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "fixedJoint", blockType: Scratch.BlockType.COMMAND, text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},RA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},RB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},}},
+ {opcode: "sphericalJoint", blockType: Scratch.BlockType.COMMAND, text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},}},
+ {opcode: "revoluteJoint", blockType: Scratch.BlockType.COMMAND, text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},X: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"},}},
+ "---",
+ {blockType: Scratch.BlockType.LABEL, text: "- Collider"},
+ {opcode: "setC", blockType: Scratch.BlockType.COMMAND, text: "set collider [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderSets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {opcode: "getC", blockType: Scratch.BlockType.REPORTER, text: "get collider [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "sensorSingle", blockType: Scratch.BlockType.BOOLEAN, text: "is sensor [SENSOR] touching [OBJECT]?", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {opcode: "sensorAll", blockType: Scratch.BlockType.REPORTER, text: "objects touching sensor [SENSOR]", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}}}
+ ],
+ menus: {
+ wProp: {acceptReporters: false, items: [
+ {text: "Gravity", value: "gravity"}, {text: "log to console", value: "log"}
+ ]},
+ tf: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true", value: "true"}]},
+ lockAxes: {acceptReporters: false, items: [
+ {text: "Translation", value: "setEnabledTranslations"}, {text: "Rotation", value: "setEnabledRotations"}
+ ]},
+ rigidBodyProperties: {acceptReporters: false, items: [
+ {text: "Type", value: "bodyType"},
+ {text: "Linear Velocity", value: "linvel"},
+ {text: "Angular Velocity", value: "angvel"},
+ {text: "Translation (position)", value: "translation"},
+ {text: "Rotation (quaternion)", value: "rotation"},
+ {text: "Mass", value: "mass"},
+ //{text: "Center of Mass", value: "centerOfMass"},
+ {text: "Linear Damping", value: "linearDamping"},
+ {text: "Angular Damping", value: "angularDamping"},
+ {text: "Is Sleeping?", value: "isSleeping"},
+ //{text: "Can Sleep?", value: "isCanSleep"},
+ {text: "Gravity Scale", value: "gravityScale"},
+ {text: "Is Fixed?", value: "isFixed"},
+ {text: "Is Dynamic?", value: "isDynamic"},
+ {text: "Is Kinematic?", value: "isKinematic"},
+ //{text: "Sleeping", value: "sleeping"}
+ ]},
+ rigidBodySets: {acceptReporters: false, items: [
+ //{text: "Linear Velocity", value: "setLinvel"},
+ //{text: "Angular Velocity", value: "setAngvel"},
+ //{text: "Mass", value: "setMass"},
+ {text: "Gravity Scale", value: "setGravityScale"},
+ //{text: "Can Sleep?", value: "setCanSleep"},
+ //{text: "Sleeping", value: "sleeping"},
+ {text: "Linear Damping", value: "setLinearDamping"},
+ {text: "Angular Damping", value: "setAngularDamping"},
+ {text: "Is Fixed?", value: "isFixed"},
+ {text: "Is Dynamic?", value: "isDynamic"},
+ {text: "Is Kinematic?", value: "isKinematic"}
+ ]},
+ colliderProperties: {acceptReporters: false, items: [
+ //{text: "Collider Type", value: "type"},
+ {text: "Is Sensor?", value: "isSensor"},
+ {text: "Friction", value: "friction"},
+ {text: "Restitution", value: "restitution"},
+ {text: "Density", value: "density"},
+ {text: "Mass", value: "mass"},
+ {text: "Position", value: "translation"},
+ {text: "Rotation", value: "rotation"},
+ //{text: "Area", value: "area"},
+ {text: "Volume", value: "volume"},
+ {text: "Collision Groups", value: "collisionGroups"},
+ //{text: "Collision Mask", value: "collisionMask"},
+ //{text: "Is Enabled?", value: "enabled"},
+ //{text: "Contact Count", value: "contactCount"},
+ //{text: "RigidBody Handle", value: "rigidBody"}
+ ]},
+ colliderSets: {acceptReporters: false, items: [
+ {text: "Friction", value: "setFriction"},
+ {text: "Restitution", value: "setRestitution"},
+ {text: "Density", value: "setDensity"},
+ {text: "Is Sensor?", value: "setSensor"},
+ {text: "Collision Groups", value: "setCollisionGroups"},
+ //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
+ //{text: "Position Offset", value: "setTranslation"},
+ //{text: "Rotation Offset", value: "setRotation"}
+ ]},
+ state: {acceptReporters: true, items: [{text: "on", value: "true"},{text: "off", value: "false"}]},
+ state2: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true (must be fixed)", value: "true"}]},
+ spaces: {acceptReporters: false, items: [{text: "World", value: "world"},{text: "Local", value: "local"}]},
+ objectTypes: {acceptReporters: false, items: [{text: "Dynamic", value: "dynamic"},{text: "Fixed", value: "fixed"},{text: "Kinematic Position Based",value: "kinematicPositionBased"}]},
+ colliderTypes: {acceptReporters: false, items: [{text: "Box, Rectangle, cuboid", value: "cuboid"},{text: "Sphere, ball", value: "ball"},{text: "Custom, complex simple shapes, convexHull", value: "convexHull"},{text:"Precision, TriMesh",value:"trimesh"}]},
+ forces: {acceptReporters: false, items: [{text: "Force", value: "addForce"},{text: "Torque (rotation)", value: "addTorque"},{text: "Apply Impulse", value: "applyImpulse"},{text: "Apply Torque Impulse (rotation)", value: "applyTorqueImpulse"},{text: "Linear Velocity", value: "setLinvel"},{text: "Angular Velocity", value: "setAngvel"},]},
+ resetF: {acceptReporters: false, items: [{text:"Forces", value: "resetForces"},{text:"Torques", value: "resetTorques"},]}
+ }
+ }
+ }
+ joint(jointData, bodyA, bodyB) {
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true)
+ }
+
+ fixedJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+ let RA = JSON.parse(args.RA).map(Number)
+ let RB = JSON.parse(args.RB).map(Number)
+
+ RA = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RA[0]),
+ THREE.MathUtils.degToRad(RA[1]),
+ THREE.MathUtils.degToRad(RA[2])
+ )
+ )
+ RB = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RB[0]),
+ THREE.MathUtils.degToRad(RB[1]),
+ THREE.MathUtils.degToRad(RB[2])
+ )
+ )
+
+ const data = RAPIER.JointData.fixed(
+ { x: VA[0], y: VA[1], z: VA[2] }, RA,
+ { x: VB[0], y: VB[1], z: VB[2] }, RB
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ sphericalJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+
+ const data = RAPIER.JointData.spherical(
+ { x: VA[0], y: VA[1], z: VA[2] },
+ { x: VB[0], y: VB[1], z: VB[2] }
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ revoluteJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+ const x = JSON.parse(args.X).map(Number)
+
+ const data = RAPIER.JointData.revolute(
+ { x: VA[0], y: VA[1], z: VA[2] },
+ { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ prismaticJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+ const x = JSON.parse(args.X).map(Number)
+
+ const data = RAPIER.JointData.prismatic(
+ { x: VA[0], y: VA[1], z: VA[2] },
+ { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ createWorld(args) {
+ const v3 = JSON.parse(args.G).map(Number)
+ const gravity = { x: v3[0], y: v3[1], z: v3[2]}
+ physicsWorld = new RAPIER.World(gravity)
+
+ console.log(physicsWorld)
+ }
+
+ getWorld(args) {
+ if (args.PROPERTY === "log") {console.log(physicsWorld); return "logged"}
+ return JSON.stringify(physicsWorld[args.PROPERTY])
+ }
+
+ setRB(args) {
+ let value = args.VALUE
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
+ let object = getObject(args.OBJECT)
+ object.rigidBody[args.PROPERTY](value)
+ }
+ setC(args) {
+ let value = args.VALUE
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
+ let object = getObject(args.OBJECT)
+ object.collider[args.PROPERTY](value)
+ }
+
+ getRB(args) {
+ let object = getObject(args.OBJECT)
+ return JSON.stringify(object.rigidBody[args.PROPERTY]())
+ }
+ getC(args) {
+ let object = getObject(args.OBJECT)
+ return JSON.stringify(object.collider[args.PROPERTY]())
+ }
+
+ lockObjectAxis(args) {
+ let object = getObject(args.OBJECT)
+ const x = !JSON.parse(args.X)
+ const y = !JSON.parse(args.Y)
+ const z = !JSON.parse(args.Z)
+ object.rigidBody[args.PROPERTY](x,y,z,true) //changes is xyz, wake up
+ }
+
+ objectPhysics(args) {
+ let object = getObject(args.OBJECT)
+ object.physics = JSON.parse(args.state)
+
+ if (JSON.parse(args.state)) {
+ //if already exists delete:
+ if (object.rigidBody) {
+ physicsWorld.removeRigidBody(object.rigidBody)
+ object.rigidBody = null
+ object.collider = null
+ }
+ /*asing a rigidbody and collider to object and add them to physicsWorld*/
+ let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
+ .setTranslation(object.position.x, object.position.y, object.position.z)
+ .setRotation({w: object.quaternion._w, x: object.quaternion._x, y: object.quaternion._y, z: object.quaternion._z})
+
+ let colliderDesc
+ switch(args.collider) {
+ case "cuboid": colliderDesc = createCuboidCollider(object,); break
+ case "ball": colliderDesc = createBallCollider(object); break
+ case "convexHull": colliderDesc = createConvexHullCollider(object); break
+ case "trimesh": colliderDesc = TriMesh(object); break
+ }
+ colliderDesc.setSensor(JSON.parse(args.state2)).setMass(args.mass).setDensity(args.density).setFriction(args.friction)
+
+ let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc)
+ let collider = physicsWorld.createCollider(colliderDesc, rigidBody)
+
+ object.rigidBody = rigidBody
+ object.collider = collider
+ } else {
+ /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
+ physicsWorld.removeRigidBody(object.rigidBody)
+ object.rigidBody = null
+ object.collider = null
+ }
+
+ }
+
+ enableCCD(args) {
+ let object = getObject(args.OBJECT)
+ if (object.physics) {
+ let rigidBody = object.rigidBody
+ rigidBody.enableCcd(JSON.parse(args.state))
+ }
+ }
+
+ addForce(args) {
+ let object = getObject(args.OBJECT)
+ const vector = JSON.parse(args.VALUE).map(Number)
+
+ let force = new THREE.Vector3(vector[0],vector[1],vector[2])
+ if (args.SPACE === "local") {
+ force.applyQuaternion(object.quaternion);
+ }
+
+ object.rigidBody[args.PROPERTY](force,true)
+ }
+
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true)
+ }
+
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR)
+
+ let object = getObject(args.OBJECT)
+
+ let touching = false
+ physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
+ if (otherCollider === object.collider) touching = true
+ })
+
+ return touching
+ }
+
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR)
+
+ const touchedObjects = []
+
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
+ // find owner of collider
+ const otherObject = scene.children.find(o => o.collider === otherCollider)
+ console.log(otherCollider)
+ if (otherObject) touchedObjects.push(otherObject.name)
+ })
+
+ return JSON.stringify(touchedObjects)
+ }
+
+ }
+ Scratch.extensions.register(new RapierPhysics())
+
+ //Thanks to the PointerLock extension of Turbowarp
+ const mouse = vm.runtime.ioDevices.mouse;
+ let isLocked = false;
+ let isPointerLockEnabled = false;
+
+ let rect = threeRenderer.domElement.getBoundingClientRect();
+ document.addEventListener("resize", () => {
+ rect = threeRenderer.domElement.getBoundingClientRect();
+ });
+
+ const postMouseData = (e, isDown) => {
+ const { movementX, movementY } = e;
+ const { width, height } = rect;
+ const x = mouse._clientX + movementX;
+ const y = mouse._clientY - movementY;
+ mouse._clientX = x;
+ mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
+ mouse._clientY = y;
+ mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
+ if (typeof isDown === "boolean") {
+ const data = {
+ button: e.button,
+ isDown,
+ };
+ originalPostIOData(data);
+ }
+ };
+
+ const mouseDevice = vm.runtime.ioDevices.mouse;
+ const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
+ mouseDevice.postData = (data) => {
+ if (!isPointerLockEnabled) {
+ return originalPostIOData(data);
+ }
+ };
+
+ document.addEventListener(
+ "mousedown",
+ (e) => {
+ // @ts-expect-error
+ if (threeRenderer.domElement.contains(e.target)) {
+ if (isLocked) {
+ postMouseData(e, true);
+ } else if (isPointerLockEnabled) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mouseup",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e, false);
+ // @ts-expect-error
+ } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mousemove",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e);
+ }
+ },
+ true
+ );
+
+ document.addEventListener("pointerlockchange", () => {
+ isLocked = document.pointerLockElement === threeRenderer.domElement;
+ });
+ document.addEventListener("pointerlockerror", (e) => {
+ console.error("Pointer lock error", e);
+ });
+
+ const oldStep = vm.runtime._step;
+ vm.runtime._step = function (...args) {
+ const ret = oldStep.call(this, ...args);
+ if (isPointerLockEnabled) {
+ const { width, height } = rect;
+ mouse._clientX = width / 2;
+ mouse._clientY = height / 2;
+ mouse._scratchX = 0;
+ mouse._scratchY = 0;
+ }
+ return ret;
+ };
+
+ vm.runtime.on("PROJECT_LOADED", () => {
+ isPointerLockEnabled = false;
+ if (isLocked) {
+ document.exitPointerLock();
+ }
+ });
+
+ class Pointerlock {
+ getInfo() {
+ return {
+ id: "threepointerlockmod",
+ name: "Pointerlock for Extra 3D",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "setLocked", blockType: Scratch.BlockType.COMMAND, text: "set pointer lock [enabled]", arguments: { enabled: { type: Scratch.ArgumentType.STRING, defaultValue: "true", menu: "enabled"}},},
+ {opcode: "isLocked", blockType: Scratch.BlockType.BOOLEAN, text: "pointer locked?",},
+ ],
+ menus: {
+ enabled: {acceptReporters: true, items: [
+ {text: "enabled", value: "true"},{text: "disabled", value: "false"},
+ ]}
+ },
+ }
+ }
+
+ setLocked(args) {
+ isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
+ if (!isPointerLockEnabled && isLocked) {
+ document.exitPointerLock();
+ }
+ }
+
+ isLocked() {
+ return isLocked;
+ }
+ }
+Scratch.extensions.register(new Pointerlock())
+
+ })
+
+
+
+
+})(Scratch);
diff --git a/threejsD_LOCAL_13852.js b/threejsD_LOCAL_13852.js
new file mode 100644
index 0000000..27ba380
--- /dev/null
+++ b/threejsD_LOCAL_13852.js
@@ -0,0 +1,2414 @@
+// Name: Extra 3D
+// ID: threejsExtension
+// Description: Use three js inside Turbowarp! A 3D graphics library.
+// By: Civero
+// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
+
+(function (Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Three-D extension must run unsandboxed");
+ }
+
+ if (Scratch.vm.runtime.isPackaged) {alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`); return}
+ //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
+
+ const vm = Scratch.vm;
+ const runtime = vm.runtime
+ const renderer = Scratch.renderer;
+ const canvas = renderer.canvas
+ const Cast = Scratch.Cast;
+ const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
+
+ let alerts = false
+ console.log("alerts are "+ (alerts ? "enabled" : "disabled"))
+
+ let isMouseDown = { left: false, middle: false, right: false }
+ let prevMouse = { left: false, middle: false, right: false }
+
+ let lastWidth = 0
+ let lastHeight = 0
+
+ let THREE
+ let clock
+ let running
+ let loopId
+ //Addons
+ let GLTFLoader
+ let gltf
+ let OrbitControls
+ let controls
+ let BufferGeometryUtils
+ let TextGeometry
+ let fontLoad
+ //Physics
+ let RAPIER
+ let physicsWorld
+
+ let threeRenderer
+ let scene
+ let camera
+ let eulerOrder = "YXZ"
+
+ let composer
+ let passes = {}
+ let customEffects = []
+ let renderTargets = {}
+
+ let materials = {}
+ let geometries = {}
+ let lights = {}
+ let models = {}
+
+ let assets = { //should i place materials, geometries; inside too?
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {}, //not the same as the global one! this one only stores textures
+ }
+
+ let raycastResult = []
+
+ function resetor(level) {
+ camera = undefined
+ composer.reset()
+
+ passes = {}
+ customEffects = []
+ renderTargets = {}
+
+ materials = {}
+ geometries = {}
+ lights = {}
+ models = {}
+
+ if (level > 0) {
+ assets = {
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {},
+ }
+ }
+
+ updateComposers()
+ }
+
+//utility
+ function vector3ToString(prop) {
+ if (!prop) return "0,0,0";
+
+ const x = (typeof(prop.x) === "number") ? prop.x : (typeof(prop._x) === "number") ? prop._x : (JSON.stringify(prop).includes("X")) ? prop : 0
+ const y = (typeof(prop.y) === "number") ? prop.y : (typeof(prop.y) === "number") ? prop._y : 0
+ const z = (typeof(prop.z) === "number") ? prop.z : (typeof(prop.z) === "number") ? prop.z : 0
+
+ return [x, y, z]
+ }
+
+//objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true)
+ if (object) {
+ removeObject(name)
+ alerts ? alert(name + " already exsisted, will replace!") : null
+ }
+ content.name = name
+ content.rotation._order = eulerOrder
+ parentName === scene.name ? object = scene : object = getObject(parentName)
+ content.physics = false
+
+ object.add(content)
+ }
+ function removeObject(name) {
+ let object = getObject(name)
+ if (!object) return
+
+ scene.remove(object)
+
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true)
+ physicsWorld.removeRigidBody(object.rigidBody, true)
+ object.rigidBody = null
+ object.collider = null
+ }
+ if (object.isLight) {
+ delete(lights[name])
+ }
+ }
+ function getObject(name, isNew) {
+ let object = null
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null; return;}
+ object = scene.getObjectByName(name)
+ if (!object && !isNew) {alerts ? alert(name + " does not exist! Add it to scene"):null; return;}
+ return object
+ }
+
+//materials
+ function encodeCostume (name) {
+ if (name.startsWith("data:image/")) return name
+ return Scratch.vm.editingTarget.sprite.costumes.find(c => c.name === name).asset.encodeDataURI()
+ }
+ function setTexutre (texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace
+
+ if (mode === "Pixelate") {
+ texture.minFilter = THREE.NearestFilter;
+ texture.magFilter = THREE.NearestFilter;
+ } else { //Blur
+ texture.minFilter = THREE.NearestMipmapLinearFilter
+ texture.magFilter = THREE.NearestMipmapLinearFilter
+ }
+
+ if (style === "Repeat") {
+ texture.wrapS = THREE.RepeatWrapping
+ texture.wrapT = THREE.RepeatWrapping
+ texture.repeat.set(x, y)
+ }
+
+ texture.generateMipmaps = true;
+ }
+ async function resizeImageToSquare(uri, size = 256) {
+ return new Promise((resolve) => {
+ const img = new Image()
+ img.onload = () => {
+ const canvas = document.createElement('canvas')
+ canvas.width = size
+ canvas.height = size
+ const ctx = canvas.getContext('2d')
+
+ // clear + draw image scaled to fit canvas
+ ctx.clearRect(0, 0, size, size)
+ ctx.drawImage(img, 0, 0, size, size)
+
+ resolve(canvas.toDataURL()) // return normalized Data URI
+ //delete canvas?
+ };
+ img.src = uri
+ });
+}
+//light
+function updateShadowFrustum(light, focusPos) {
+ if (light.type !== "DirectionalLight") return
+
+ // Frustum Size - Increase this value to cover a larger area.
+ const d = 50;
+
+ // Update Orthographic Shadow Camera Frustum
+ const shadowCamera = light.shadow.camera;
+
+ // Set the width/height of the frustum
+ shadowCamera.left = -d;
+ shadowCamera.right = d;
+ shadowCamera.top = d;
+ shadowCamera.bottom = -d;
+
+ // Determine ranges
+ shadowCamera.near = 0.1
+ shadowCamera.far = 500
+
+ // Position the Light and its Target
+ light.target.position.copy(focusPos);
+ const direction = light.position.clone().sub(light.target.position).normalize();
+ light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
+
+ // Ensure matrices are updated.
+ light.target.updateMatrixWorld();
+ light.shadow.camera.updateProjectionMatrix()
+ light.shadow.needsUpdate = true;
+}
+//composer
+function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
+
+ // always recreate the RenderPass to point to the current scene/camera
+ passes["Render"] = new RenderPass(scene, camera);
+
+ // ensure composer has a RenderPass as the first pass
+ const hasRender = composer.passes.some(p => p && p.scene);
+ if (!hasRender) composer.addPass(passes["Render"]);
+ else {
+ // if composer already has one, replace it so it references current scene/camera
+ const idx = composer.passes.findIndex(p => p && p.scene);
+ composer.passes[idx] = passes["Render"];
+ }
+}
+//utility
+function getMouseNDC(event) {
+ // Use threeRenderer.domElement for correct offset
+ const rect = threeRenderer.domElement.getBoundingClientRect();
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ return [x, y];
+}
+function checkCanvasSize() {
+ const { width, height } = canvas
+ if (width !== lastWidth || height !== lastHeight) {
+ lastWidth = width
+ lastHeight = height
+ resize()
+ }
+ requestAnimationFrame(checkCanvasSize) //rerun next frame
+}
+//physics
+function computeWorldBoundingBox(mesh) {
+ // Create a Box3 in world coordinates
+ const box = new THREE.Box3().setFromObject(mesh);
+ const size = new THREE.Vector3();
+ box.getSize(size);
+ const center = new THREE.Vector3();
+ box.getCenter(center);
+ return { size, center };
+}
+function createCuboidCollider(mesh) {
+ const { size } = computeWorldBoundingBox(mesh);
+ const collider = RAPIER.ColliderDesc.cuboid(
+ size.x / 2,
+ size.y / 2,
+ size.z / 2
+ )
+ return collider;
+}
+function createBallCollider(mesh) {
+ const { size } = computeWorldBoundingBox(mesh);
+ // radius = 1/2 of the largest verticie
+ const radius = Math.max(size.x, size.y, size.z) / 2;
+ const collider = RAPIER.ColliderDesc.ball(radius)
+ return collider //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
+}
+function createConvexHullCollider(mesh) {
+ mesh.updateWorldMatrix(true, false);
+
+ const position = mesh.geometry.attributes.position;
+ const vertices = [];
+ const vertex = new THREE.Vector3();
+
+ // Matrix for scale only
+ const scaleMatrix = new THREE.Matrix4().makeScale(
+ mesh.scale.x,
+ mesh.scale.y,
+ mesh.scale.z
+ );
+
+ for (let i = 0; i < position.count; i++) {
+ vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
+ vertices.push(vertex.x, vertex.y, vertex.z);
+ }
+
+ const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
+ return collider;
+}
+function TriMesh(mesh) {
+ // Get the positions array (from your geoPoints function)
+const positions = mesh.geometry.attributes.position.array;
+const numVertices = positions.length / 3;
+
+// Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
+const indices = Array.from({ length: numVertices }, (_, i) => i);
+
+const collider = RAPIER.ColliderDesc.trimesh(
+ positions,
+ new Uint32Array(indices)
+);
+
+return collider
+}
+function getModel(model, name) {
+ const file = runtime.getTargetForStage().getSounds().find(c => c.name === model)
+ if (!file) return
+
+return new Promise((resolve, reject) => {
+ gltf.parse(
+ file.asset.data.buffer,
+ "",
+ gltf => {
+ const root = gltf.scene
+ root.traverse(child => {
+ if (child.isMesh) {
+ child.castShadow = true
+ child.receiveShadow = true
+ }
+ });
+
+ const mixer = new THREE.AnimationMixer(root)
+ const actions = {}
+ gltf.animations.forEach(clip => {
+ const act = mixer.clipAction(clip)
+ act.clampWhenFinished = true
+ actions[clip.name] = act
+ });
+
+ models[name] = { root, mixer, actions }
+ resolve(root)
+ },
+ error => {
+ console.error("Error parsing GLB model:", error)
+ reject(error)
+ }
+ )})
+}
+async function openFileExplorer(format) {
+ return new Promise((resolve) => {
+ const input = document.createElement("input");
+ input.type = "file"
+ input.accept = format
+ input.multiple = false
+ input.onchange = () => {
+ resolve(input.files)
+ input.remove()
+ };
+ input.click();
+ })
+}
+function getMeshesUsingTexture(scene, targetTexture) {
+ const meshes = []
+
+ scene.traverse(object => {
+ if (object.material) {
+ const materials = Array.isArray(object.material) ? object.material : [object.material]
+ for (const material of materials) {
+ if (material.map === targetTexture) {
+ meshes.push(object)
+ break
+ }
+ }
+ }
+ })
+
+ return meshes
+}
+function getAsset(path) {
+ if (typeof(path) == "string") { //string?
+ if (path.includes("/")) { //has the /?
+ const value = path.split("/")
+ console.log(value[0], value[1])
+ return assets[value[0]][value[1]]
+ }
+ }
+
+ return JSON.parse(path) //boolean or number
+}
+
+let mouseNDC = [0, 0]
+//loops/init
+function stopLoop() {
+ if (!running) return
+ running = false
+
+ if (loopId) {
+ cancelAnimationFrame(loopId)
+ loopId = null
+ if (threeRenderer) threeRenderer.clear();
+ }
+}
+async function load() {
+ if (!THREE) {
+
+ // @ts-ignore
+ THREE = await import("https://esm.sh/three@0.180.0")
+ window._THREE_ = THREE
+ //Addons
+ GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js")
+ OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js")
+ BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js")
+ TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js")
+ const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js")
+ fontLoad = new FontLoader.FontLoader()
+
+ const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8")
+ const {
+ EffectComposer,
+ EffectPass,
+ RenderPass,
+
+ Effect,
+ BloomEffect,
+ GodRaysEffect,
+ DotScreenEffect,
+ DepthOfFieldEffect,
+
+ BlendFunction
+ } = POSTPROCESSING
+ //so i can use them later as global
+ window.EffectComposer = EffectComposer;
+ window.EffectPass = EffectPass;
+ window.RenderPass = RenderPass;
+ window.Effect = Effect;
+ window.BloomEffect = BloomEffect;
+ window.GodRaysEffect = GodRaysEffect;
+ window.DotScreenEffect = DotScreenEffect;
+ window.DepthOfFieldEffect = DepthOfFieldEffect;
+ window.BlendFunction = BlendFunction;
+
+ RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0")
+ await RAPIER.init()
+
+ threeRenderer = new THREE.WebGLRenderer({
+ powerPreference: "high-performance",
+ antialias: false,
+ stencil: false,
+ depth: true
+ })
+ threeRenderer.setPixelRatio(window.devicePixelRatio)
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping // HDR look (test)
+ //threeRenderer.toneMappingExposure = 1.0 //(test)
+
+ threeRenderer.shadowMap.enabled = true
+ threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap // (optional)
+ threeRenderer.domElement.style.pointerEvents = 'auto' //will disable turbowarp mouse events, but enable threejs's
+
+ gltf = new GLTFLoader.GLTFLoader()
+ clock = new THREE.Clock()
+
+ // Example: create a composer
+ composer = new EffectComposer(threeRenderer, {frameBufferType: THREE.HalfFloatType})
+
+ renderer.addOverlay( threeRenderer.domElement, "manual" )
+ renderer.addOverlay(canvas, "manual")
+ renderer.setBackgroundColor(1, 1, 1, 0)
+
+ resize()
+
+ window.addEventListener("mousedown", e => {
+ if (e.button === 0) isMouseDown.left = true
+ if (e.button === 1) isMouseDown.middle = true
+ if (e.button === 2) isMouseDown.right = true
+ })
+ window.addEventListener("mouseup", e => {
+ if (e.button === 0) isMouseDown.left = false; prevMouse.left = false
+ if (e.button === 1) isMouseDown.middle = false; prevMouse.middle = false
+ if (e.button === 2) isMouseDown.right = false; prevMouse.right = false
+ })
+ // prevent contextmenu on right click
+ threeRenderer.domElement.addEventListener("contextmenu", e => e.preventDefault());
+
+ threeRenderer.domElement.addEventListener('mousemove', (event) => {
+ mouseNDC = getMouseNDC(event);
+ })
+
+ running = false
+ load()
+
+ startRenderLoop()
+ runtime.on('PROJECT_START', () => startRenderLoop())
+ runtime.on('PROJECT_STOP_ALL', () => stopLoop())
+ runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
+ checkCanvasSize()
+ }
+ }
+function startRenderLoop() {
+ if (running) return
+ running = true
+
+ const loop = () => {
+ if (!running) return
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step()
+
+ scene.children.forEach(obj => {
+ if (!(obj.isMesh) || !(obj.physics)) return
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation())
+ obj.quaternion.copy(obj.rigidBody.rotation())
+ }
+ })
+
+ }
+ if (scene && camera) {
+ if (controls) controls.update()
+
+ const delta = clock.getDelta()
+ Object.values(models).forEach( model => { if (model) model.mixer.update(delta) } )
+
+ Object.values(lights).forEach(light => updateShadowFrustum(light, camera.position))
+
+ //update custom effects time
+ customEffects.forEach(e => {
+ if (e.uniforms.get('time')) {
+ e.uniforms.get('time').value += delta
+ }
+ })
+ Object.values(renderTargets).forEach(t => {
+ if ( t.camera.type == "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height
+ t.camera.updateProjectionMatrix()
+ }
+ // get meshes using the texture associated with this target
+ const displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
+
+ displayMeshes.forEach(mesh => {
+ mesh.visible = false
+ })
+
+ if (t.camera.type == "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target)
+ threeRenderer.clear(true, true, true)
+ threeRenderer.render(scene, t.camera)
+ } else {
+ t.target.clear(threeRenderer)
+ t.camera.update( threeRenderer, scene ) //cubeCamera
+ }
+
+ displayMeshes.forEach(mesh => {
+ mesh.visible = true
+ })
+ })
+
+ camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height
+ camera.updateProjectionMatrix()
+ threeRenderer.setRenderTarget(null)
+ composer.render(delta)
+ }
+
+ loopId = requestAnimationFrame(loop)
+ }
+
+ loopId = requestAnimationFrame(loop)
+}
+
+function resize() {
+ const w = canvas.width
+ const h = canvas.height
+
+ threeRenderer.setSize(w, h)
+ composer.setSize(w, h)
+ customEffects.forEach(e => {
+ if (e.uniforms.get('resolution')) {
+ e.uniforms.get('resolution').value.set(w,h)
+ }
+ })
+
+ if (camera) {
+ camera.aspect = w / h
+ camera.updateProjectionMatrix()
+}
+}
+//wait until all packages are loaded
+Promise.resolve(load()).then(() => {
+
+ class threejsExtension {
+ getInfo() {
+ return {
+ id: "threejsExtension",
+ name: "Extra 3D",
+ color1: "#222222",
+ color2: "#222222",
+ color3: "#11cc99",
+ menuIconURI,
+ blockIconURI: menuIconURI,
+
+ blocks: [
+ {blockType: Scratch.BlockType.BUTTON, text: "Show Docs", func: "openDocs"},
+ {blockType: Scratch.BlockType.BUTTON, text: "Toggle Alerts", func: "alerts"},
+ ],
+ menus: {}
+ }}
+ openDocs(){
+ open("https://civ3ro.github.io/extensions/Documentation/")
+ }
+ alerts() {alerts = !alerts; alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!")}
+ }
+ Scratch.extensions.register(new threejsExtension())
+
+ class ThreeRenderer {
+ getInfo() {
+ return {
+ id: "threeRenderer",
+ name: "Three Renderer",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "setRendererRatio", blockType: Scratch.BlockType.COMMAND, text: "set Pixel Ratio to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"}}},
+ {opcode: "eulerOrder", blockType: Scratch.BlockType.COMMAND, text: "set euler order to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "YXZ"}}},
+ ],
+ menus: {}
+ }}
+
+ setRendererRatio(args) {
+ threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE)
+ }
+ eulerOrder(args) {
+ eulerOrder = args.VALUE
+ console.log("euler order set to", eulerOrder)
+ }
+
+ }
+ Scratch.extensions.register(new ThreeRenderer())
+
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
+ getInfo() {
+ return {
+ id: "threeScene",
+ name: "Three Scene",
+ color1: "#4638c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "newScene", blockType: Scratch.BlockType.COMMAND, text: "new Scene [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}}},
+
+ {opcode: "setSceneProperty", blockType: Scratch.BlockType.COMMAND, text: "set Scene [PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "sceneProperties", defaultValue: "background"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
+ "---",
+ {opcode: "getSceneObjects", blockType: Scratch.BlockType.REPORTER, text: "get Scene [THING]", arguments:{THING: {type: Scratch.ArgumentType.STRING, menu: "sceneThings"}}},
+ {opcode: "reset", blockType: Scratch.BlockType.COMMAND, text: "Reset Everything"}
+ ],
+ menus: {
+ sceneProperties: {acceptReporters: false, items: [
+ {text: "Background", value: "background"},{text: "Background Blurriness", value: "backgroundBlurriness"},{text: "Background Intensity", value: "backgroundIntensity"},{text: "Background Rotation", value: "backgroundRotation"},
+ {text: "Environment", value: "environment"},{text: "Environment Intensity", value: "environmentIntensity"},{text: "Environment Rotation", value: "environmentRotation"},{text: "Fog", value: "fog"},
+ ]},
+ sceneThings: {acceptReporters: false, items: ["Objects", "Materials", "Geometries","Lights","Scene Properties","Other assets"]},
+
+ }
+ }}
+
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME
+ scene.background = new THREE.Color("#222")
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {...this.scenes, {scene}};
+ resetor(0)
+ }
+
+ reset() {
+ resetor(1)
+ }
+
+ async setSceneProperty(args) {
+ const property = args.PROPERTY;
+ const value = getAsset(args.VALUE);
+
+ scene[property] = value;
+ }
+ getSceneObjects(args){
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse(obj => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ }
+ else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials))
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries))
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights))
+ else if (args.THING === "Scene Properties") {console.log(scene); return "check console"}
+ else if (args.THING === "Other assets") return JSON.stringify(assets)
+
+ return JSON.stringify(names); // if objects
+ }
+
+ }
+ Scratch.extensions.register(new ThreeScene())
+
+ class ThreeCameras {
+ getInfo() {
+ return {
+ id: "threeCameras",
+ name: "Three Cameras",
+ color1: "#38c59bff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "addCamera", blockType: Scratch.BlockType.COMMAND, text: "add camera [TYPE] [CAMERA] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "cameraTypes"}}},
+ {opcode: "setCamera", blockType: Scratch.BlockType.COMMAND, text: "set camera [PROPERTY] of [CAMERA] to [VALUE]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "0.1", exemptFromNormalization: true}}},
+ {opcode: "getCamera", blockType: Scratch.BlockType.REPORTER, text: "get camera [PROPERTY] of [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}}},
+ "---",
+ {opcode: "renderSceneCamera", blockType: Scratch.BlockType.COMMAND, text: "set rendering camera to [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}}},
+ "---",
+ {opcode: "cubeCamera", blockType: Scratch.BlockType.COMMAND, text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "cubeCamera"}, GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
+ "---",
+ {opcode: "renderTarget", blockType: Scratch.BlockType.COMMAND, text: "set a RenderTarget: [RT] for camera [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
+ {opcode: "sizeTarget", blockType: Scratch.BlockType.COMMAND, text: "set RenderTarget [RT] size to [W] [H]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, W: {type: Scratch.ArgumentType.NUMBER, defaultValue: 480}, H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 360},} },
+ {opcode: "getTarget", blockType: Scratch.BlockType.REPORTER, text: "get RenderTarget: [RT] texture", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
+ {opcode: "removeTarget", blockType: Scratch.BlockType.COMMAND, text: "remove RenderTarget: [RT]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
+ ],
+ menus: {
+ cameraTypes: {acceptReporters: false, items: [
+ {text: "Perspective", value: "PerspectiveCamera"},
+ ]},
+ cameraProperties: {acceptReporters: false, items: [
+ {text: "Near", value: "near"},{text: "Far", value: "far"},{text: "FOV", value: "fov"},{text: "Focus (nothing...)", value: "focus"},{text: "Zoom", value: "zoom"},
+ ]},
+ }
+ }}
+ addCamera(args) {
+ let v2 = new THREE.Vector2()
+ threeRenderer.getSize(v2)
+ const object = new THREE.PerspectiveCamera(90, v2.x / v2.y )
+ object.position.z = 3
+
+ createObject(args.CAMERA, object, args.GROUP)
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA)
+ object[args.PROPERTY] = args.VALUE
+ object.updateProjectionMatrix()
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA)
+ const value = JSON.stringify(object[args.PROPERTY])
+ return value
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA)
+ if (!object) return
+ camera = object
+ //reset composer, else it does not update.
+ composer.passes = []
+ passes = {}
+ customEffects = []
+ updateComposers()
+ }
+
+ cubeCamera(args) {
+ // Create cube render target
+ const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true } )
+ // Create cube camera
+ const cubeCamera = new THREE.CubeCamera( 0.1, 500, cubeRenderTarget )
+ createObject(args.CAMERA, cubeCamera, args.GROUP)
+
+ renderTargets[args.RT] = {target: cubeRenderTarget, camera: cubeCamera}
+ assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture
+ }
+
+ renderTarget(args) {
+ let object = getObject(args.CAMERA)
+ const renderTarget = new THREE.WebGLRenderTarget(
+ 360,
+ 360,
+ {
+ generateMipmaps: false
+ }
+ )
+
+ renderTargets[args.RT] = {target: renderTarget, camera: object}
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H)
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture
+ console.log(t, renderTargets[args.RT])
+ return `renderTargets/${t.uuid}`
+ }
+ removeTarget(args) {
+ delete(assets.renderTargets[renderTargets[args.RT].target.texture.uuid])
+ renderTargets[args.RT].target.dispose()
+ delete(renderTargets[args.RT])
+ }
+ }
+ Scratch.extensions.register(new ThreeCameras())
+
+ class ThreeObjects {
+ getInfo() {
+ return {
+ id: "threeObjects",
+ name: "Three Objects",
+ color1: "#38c567ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "addObject", blockType: Scratch.BlockType.COMMAND, text: "add object [OBJECT3D] [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},TYPE: {type: Scratch.ArgumentType.STRING, menu: "objectTypes"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {opcode: "cloneObject", blockType: Scratch.BlockType.COMMAND, text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myClone"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "setObject", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of object [OBJECT3D] to [NAME]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
+ {opcode: "getObject", blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of object [OBJECT3D]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}}},
+ {opcode: "objectE", blockType: Scratch.BlockType.BOOLEAN, text: "is there an object [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "removeObject", blockType: Scratch.BlockType.COMMAND, text: "remove object [OBJECT3D] from scene", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+
+ {blockType: Scratch.BlockType.LABEL, text: " ↳ Transforms"},
+ {opcode: "setObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
+ //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {opcode: "getObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of [OBJECT3D]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+
+ {blockType: Scratch.BlockType.LABEL, text: "↳ Materials"},
+ {opcode: "newMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "new material [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "materialTypes", defaultValue: "MeshStandardMaterial"}}},
+ {opcode: "materialE",extensions: ["colours_looks"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a material [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
+ {opcode: "removeMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "remove material [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
+ {opcode: "setMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [PROPERTY] of [NAME] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "materialProperties", defaultValue: "color"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
+ {opcode: "setBlending",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] blending to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "blendModes"}}},
+ {opcode: "setDepth",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] depth to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "depthModes"}}},
+
+ {blockType: Scratch.BlockType.LABEL, text: "↳ Geometries"},
+ {opcode: "newGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new geometry [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "geometryTypes", defaultValue: "BoxGeometry"}}},
+ {opcode: "geometryE",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a geometry [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
+ {opcode: "removeGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "remove geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
+ "---",
+ {opcode: "newGeo",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new empty geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
+ {opcode: "geoPoints",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] vertex points to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
+ {opcode: "geoUVs",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] UVs to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[UVs]"}}},
+ "---",
+ {opcode: "splines", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create spline [NAME] from curve [CURVE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}}},
+ {opcode: "splineModel", extensions: ["colours_operators"], blockType: Scratch.BlockType.COMMAND, text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, MODEL: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}, SPACING: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
+ "---",
+ {blockType: Scratch.BlockType.BUTTON, text: "Convert font to JSON", func: "openConv"},
+ {blockType: Scratch.BlockType.BUTTON, text: "Load JSON font file", func: "loadFont"},
+ {opcode: "text", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myText"}, TEXT: {type: Scratch.ArgumentType.STRING, defaultValue: "C-369"}, FONT: {type: Scratch.ArgumentType.STRING, menu: "fonts"}, S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, D: {type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1}, CS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 6}}},
+ ],
+ menus: {
+ objectVector3: {acceptReporters: false, items: [
+ {text: "Positon", value: "position"},{text: "Rotation", value: "rotation"},{text: "Scale", value: "scale"},{text: "Facing Direction (.up)", value: "up"}
+ ]},
+ objectProperties: {acceptReporters: false, items: [
+ {text: "Geometry", value: "geometry"},{text: "Material", value: "material"},{text: "Visible (true/false)", value: "visible"},
+ ]},
+ objectTypes: { acceptReporters: false, items: [
+ { text: "Mesh", value: "Mesh" }, { text: "Sprite", value: "Sprite" }, { text: "Points", value: "Points" }, { text: "Line", value: "Line" }, { text: "Group", value: "Group" }
+ ]},
+ XYZ: {acceptReporters: false, items: [{text: "X", value: "x"},{text: "Y", value: "y"},{text: "Z", value: "z"}]},
+ materialProperties: {acceptReporters: false, items: [
+ "|GENERAL| <-- not a property",
+ { text: "Color", value: "color" },
+ { text: "Map", value: "map" },
+ { text: "Opacity", value: "opacity" },
+ { text: "Transparent", value: "transparent" },
+ { text: "Alpha Map", value: "alphaMap" },
+ { text: "Alpha Test", value: "alphaTest" },
+ { text: "Depth Test", value: "depthTest" },
+ { text: "Depth Write", value: "depthWrite" },
+ { text: "Color Write", value: "colorWrite" },
+ { text: "Side", value: "side" },
+ { text: "Visible", value: "visible" },/*
+ { text: "Blending", value: "blending" },
+ { text: "Blend Src", value: "blendSrc" },
+ { text: "Blend Dst", value: "blendDst" },
+ { text: "Blend Equation", value: "blendEquation" },
+ { text: "Blend Src Alpha", value: "blendSrcAlpha" },
+ { text: "Blend Dst Alpha", value: "blendDstAlpha" },
+ { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
+ { text: "Blend Aplha", value: "blendAplha" },
+ { text: "Blend Color", value: "blendColor" },
+ { text: "Alpha Hash", value: "alphaHash" },
+ { text: "Premultiplied Alpha", value: "premultipliedAlpha" },
+
+ { text: "Tone Mapped", value: "toneMapped" },
+ { text: "Fog", value: "fog" },
+ { text: "Flat Shading", value: "flatShading" },
+
+ "|MESH Standard / Physical| <-- not a property",
+ { text: "Metalness", value: "metalness" },
+ { text: "Metalness Map", value: "metalnessMap" },
+ { text: "Roughness", value: "roughness" },
+ { text: "Reflectivity", value: "reflectivity" },
+ { text: "Roughness Map", value: "roughnessMap" },
+ { text: "Emissive", value: "emissive" },
+ { text: "Emissive Intensity", value: "emissiveIntensity" },
+ { text: "Emissive Map", value: "emissiveMap" },
+ { text: "Env Map", value: "envMap" },
+ { text: "Env Map Intensity", value: "envMapIntensity" },
+ { text: "Env Map Rotation", value: "envMapRotation" },
+ { text: "Ior", value: "ior" },
+ { text: "Refraction Ratio", value: "refractionRatio" },
+ { text: "Clearcoat", value: "clearcoat" },
+ { text: "Clearcoat Map", value: "clearcoatMap" },
+ { text: "Clearcoat Roughness", value: "clearcoatRoughness" },
+ { text: "Clearcoat Roughness Map", value: "clearcoatRoughnessMap" },
+ { text: "Dispersion", value: "dispersion" },
+ { text: "Sheen", value: "sheen" },
+ { text: "Sheen Color", value: "sheenColor" },
+ { text: "Sheen Color Map", value: "sheenColorMap" },
+ { text: "Sheen Roughness", value: "sheenRoughness" },
+ { text: "Sheen Roughness Map", value: "sheenRoughnessMap" },
+ { text: "Specular Color", value: "specularColor" },
+ { text: "Specular Color Map", value: "specularColorMap" },
+ { text: "Specular Intensity", value: "specularIntensity" },
+ { text: "Specular Intensity Map", value: "specularIntensityMap" },
+ { text: "Transmission", value: "transmission" },
+ { text: "Transmission Map", value: "transmissionMap" },
+ { text: "Thickness", value: "thickness" },
+ { text: "Thickness Map", value: "thicknessMap" },
+ { text: "Anisotropy", value: "anisotropy" },
+ { text: "Anisotropy Map", value: "anisotropyMap" },
+ { text: "Anisotropy Rotation", value: "anisotropyRotation" },
+ { text: "Attenuation Distance", value: "attenuationDistance" },
+ { text: "Attenuation Color", value: "attenuationColor" },
+ { text: "Thickness", value: "thickness" },
+ { text: "Iridescence", value: "iridescence" },
+ { text: "Iridescence Ior", value: "iridescenceIOR" },
+ { text: "Iridescence Map", value: "iridescenceMap" },
+ { text: "Iridescence Thickness Range", value: "iridescenceThicknessRange" },
+
+ "|MESH Displacement / Normal / Bump| <-- not a property",
+ { text: "Displacement Map", value: "displacementMap" },
+ { text: "Displacement Scale", value: "displacementScale" },
+ { text: "Displacement Bias", value: "displacementBias" },
+ { text: "Bump Map", value: "bumpMap" },
+ { text: "Bump Scale", value: "bumpScale" },
+ { text: "Normal Map Type", value: "normalMapType" },
+
+ "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
+ { text: "Shininess", value: "shininess" },
+
+ { text: "Wireframe", value: "wireframe" },
+ { text: "Wireframe Linewidth", value: "wireframeLinewidth" },
+ { text: "Wireframe Linecap", value: "wireframeLinecap" },
+ { text: "Wireframe Linejoin", value: "wireframeLinejoin" },
+
+ "|POINTS| <-- not a property",
+ { text: "Size", value: "size" },
+ { text: "Size Attenuation", value: "sizeAttenuation" },
+
+ "|LINES| <-- not a property",
+ { text: "Scale", value: "scale" },
+ { text: "Dash Size", value: "dashSize" },
+ { text: "Gap Size", value: "gapSize" },
+
+ "|SPRITES| <-- not a property",
+ { text: "Rotation", value: "rotation" }
+]},
+ blendModes: {acceptReporters: false, items: [
+ { text: "No Blending", value: "NoBlending" },{ text: "Normal Blending", value: "NormalBlending" },{ text: "Additive Blending", value: "AdditiveBlending" },{ text: "Subtractive Blending", value: "SubtractiveBlending" },{ text: "Multiply Blending", value: "MultiplyBlending" },{ text: "Custom Blending", value: "CustomBlending" }
+ ]},
+ depthModes: {acceptReporters: false, items: [
+ { text: "Never Depth", value: "NeverDepth" },{ text: "Always Depth", value: "AlwaysDepth" },{ text: "Equal Depth", value: "EqualDepth" },{ text: "Less Depth", value: "LessDepth" },{ text: "Less Equal Depth", value: "LessEqualDepth" },{ text: "Greater Equal Depth", value: "GreaterEqualDepth" },{ text: "Greater Depth", value: "GreaterDepth" },{ text: "Not Equal Depth", value: "NotEqualDepth" }
+ ]},
+ materialTypes:{acceptReporters: false, items: [
+ {text:"Mesh Basic Material",value:"MeshBasicMaterial"},{text:"Mesh Standard Material",value:"MeshStandardMaterial"},{text:"Mesh Physical Material",value:"MeshPhysicalMaterial"},{text:"Mesh Lambert Material",value:"MeshLambertMaterial"},{text:"Mesh Phong Material",value:"MeshPhongMaterial"},{text:"Mesh Depth Material",value:"MeshDepthMaterial"},{text:"Mesh Normal Material",value:"MeshNormalMaterial"},{text:"Mesh Matcap Material",value:"MeshMatcapMaterial"},{text:"Mesh Toon Material",value:"MeshToonMaterial"},{text:"Line Basic Material",value:"LineBasicMaterial"},{text:"Line Dashed Material",value:"LineDashedMaterial"},{text:"Points Material",value:"PointsMaterial"},{text:"Sprite Material",value:"SpriteMaterial"},{text:"Shadow Material",value:"ShadowMaterial"}
+ ]},
+ textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
+ textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
+ geometryTypes: {acceptReporters: false, items: [
+ {text: "Box Geometry", value: "BoxGeometry"},{text: "Sphere Geometry", value: "SphereGeometry"},{text: "Cylinder Geometry", value: "CylinderGeometry"},{text: "Plane Geometry", value: "PlaneGeometry"},{text: "Circle Geometry", value: "CircleGeometry"},{text: "Torus Geometry", value: "TorusGeometry"},{text: "Torus Knot Geometry", value: "TorusKnotGeometry"},
+ ]},
+ modelsList: {acceptReporters: false, items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
+ if (models.length < 1) return [["Load a model! (GLB Loader category)"]]
+
+ // @ts-ignore
+ return models.map( m => [m.name] )
+ }},
+ fonts: {acceptReporters: false, items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.json'))
+ if (models.length < 1) return [["Load a font!"]]
+
+ // @ts-ignore
+ return models.map( m => [m.name] )
+ }},
+
+ }
+ }}
+
+ addObject(args) {
+ const object = new THREE[args.TYPE]();
+
+ object.castShadow = true
+ object.receiveShadow = true
+
+ createObject(args.OBJECT3D, object, args.GROUP)
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D)
+ const clone = object.clone(true)
+ clone.name
+ createObject(args.NAME, clone, args.GROUP)
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
+
+ function degToRad(deg) {
+ return deg * Math.PI / 180;
+ }
+
+
+ if (object.rigidBody) {
+ const x = values[0]
+ const y = values[1]
+ const z = values[2]
+ if (args.PROPERTY === "rotation") {
+ const euler = new THREE.Euler(
+ degToRad(x),
+ degToRad(y),
+ degToRad(z),
+ 'YXZ'
+ )
+ const quaternion = new THREE.Quaternion()
+ quaternion.setFromEuler(euler)
+
+ object.rigidBody.setRotation({
+ x: quaternion.x,
+ y: quaternion.y,
+ z: quaternion.z,
+ w: quaternion.w
+ });
+ } else if (args.PROPERTY === "position") {
+ object.rigidBody.setTranslation({ x: x, y: y, z: z }, true)
+ }
+ return
+ }
+
+ if (object.isCamera == true && controls) {
+
+ }
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.set(0,0,0)
+ }
+ if (object.isDirectionalLight == true) {object.pos = new THREE.Vector3(...values); console.log(true, values, object.pos); return}
+ object[args.PROPERTY].set(...values);
+
+ if (object.type == "CubeCamera") object.updateCoordinateSystem()
+ }
+ /*
+ changeObjectV3(args) {
+ getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.x += values[0]
+ object.rotation.y += values[1]
+ object.rotation.z += values[2]
+ }
+ else {
+ object[args.PROPERTY].add(...values);
+ }
+ }
+ changeObjectXV3(args) {
+ getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "rotation") value = value * Math.PI / 180
+
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D)
+ if (!object) return
+ let values = vector3ToString(object[args.PROPERTY])
+ if (args.PROPERTY === "rotation") {
+ const toDeg = Math.PI/180
+ values = [values[0]/toDeg,values[1]/toDeg,values[2]/toDeg,]
+ }
+
+ return JSON.stringify(values)
+ }
+ setObject(args){
+ let object = getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "material") {const mat = materials[args.NAME]; if (mat) value = mat; else value = undefined}
+ else if (args.PROPERTY === "geometry") {const geo = geometries[args.NAME]; if (geo) value = geo; else value = undefined}
+ else value = !!value
+
+ if (value == undefined) return //invalid geo/mat
+ object[args.PROPERTY] = value
+ }
+ getObject(args){
+ let object = getObject(args.OBJECT3D)
+ if (!object) return
+ let value
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D)
+ }
+ objectE(args) {
+ return scene.children.map(o => o.name).includes(args.NAME)
+ }
+
+//defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert ("material already exists! will replace...")
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
+
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof(args.VALUE) == "string" && args.VALUE.at(0) == "|") return
+ const mat = materials[args.NAME]
+
+ let value = args.VALUE
+
+ if (args.VALUE == "false") value = false
+
+ if (args.PROPERTY == "side") {value = (args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide)}
+ else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE))
+ else value = getAsset(value)
+
+
+ console.log("o:", args.VALUE, typeof(args.VALUE))
+ console.log("r:", value, typeof(value))
+
+ mat[args.PROPERTY] = await (value) //await incase its a texture
+ mat.needsUpdate = true
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME]
+ mat.blending = THREE[args.VALUE]
+ mat.premultipliedAlpha = true
+ mat.needsUpdate = true
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME]
+ mat.depthFunc = THREE[args.VALUE]
+ mat.needsUpdate = true
+ }
+ removeMaterial(args){
+ const mat = materials[args.NAME]
+ mat.dispose()
+ delete(materials[args.NAME])
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false
+ }
+
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert ("geometry already exists! will replace...")
+ const geo = new THREE[args.TYPE]()
+ geo.name = args.NAME
+
+ geometries[args.NAME] = geo
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME]
+ geo[args.PROPERTY] = (args.VALUE)
+
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args){
+ const geo = geometries[args.NAME]
+ geo.dispose()
+ delete(geometries[args.NAME])
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false
+ }
+
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry()
+ geometry.name = args.NAME
+ geometries[args.NAME] = geometry
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME]
+ const positions = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v3 of each vertex of each triangle
+
+ geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
+ geometry.computeVertexNormals()
+
+ geometry.needsUpdate = true
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME]
+ const UVs = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v2 of each UV of each triangle
+
+ geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(UVs), 2))
+ geometry.needsUpdate = true
+ }
+
+ splines(args) {
+ const geometry = new THREE.TubeGeometry(getAsset(args.CURVE))
+ geometry.name = args.NAME
+
+ geometries[args.NAME] = geometry
+ }
+
+ async splineModel(args) {
+ const model = await getModel(args.MODEL, args.NAME)
+ if (!model) return console.warn("Model not found:", args.MODEL)
+
+ const curve = getAsset(args.CURVE)
+ const spacing = parseFloat(args.SPACING) || 1
+ const curveLength = curve.getLength()
+ const divisions = Math.floor(curveLength / spacing)
+
+ const geomList = []
+ const matList = []
+
+ for (let i = 0; i <= divisions; i++) {
+ const t = i / divisions
+ const pos = curve.getPointAt(t)
+ const tangent = curve.getTangentAt(t)
+
+ const temp = model.clone(true)
+ temp.position.copy(pos)
+
+ const up = new THREE.Vector3(0, 0, 1)
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize())
+ temp.quaternion.copy(quat)
+
+ temp.updateMatrixWorld(true)
+
+ temp.traverse(child => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone()
+ geom.applyMatrix4(child.matrixWorld)
+ geomList.push(geom)
+ matList.push(child.material) //.clone() ?
+ }
+ })
+ }
+
+ const validGeoms = geomList.filter(g => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position
+ if (!ok) console.warn("geometry skipped:", g)
+ return ok
+ })
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true)
+ merged.computeBoundingBox()
+ merged.computeBoundingSphere()
+
+ merged.name = args.NAME
+ geometries[args.NAME] = merged
+ matList.name = args.NAME
+ materials[args.NAME] = matList
+ }
+
+ async text(args) {
+ const fontFile = runtime.getTargetForStage().getSounds().find(c => c.name === args.FONT)
+ if (!fontFile) return
+
+ const json = new TextDecoder().decode(fontFile.asset.data.buffer)
+ const fontData = JSON.parse(json)
+
+ const font = fontLoad.parse(fontData)
+
+ const params = {font: font, size: JSON.parse(args.S), height: JSON.parse(args.D), curveSegments: JSON.parse(args.CS), bevelEnabled: false}
+ const geometry = new TextGeometry.TextGeometry(args.TEXT, params)
+ geometry.computeVertexNormals()
+ geometry.center() // optional, recenters the text
+
+
+ geometry.name = args.NAME
+
+ geometries[args.NAME] = geometry
+ }
+
+ async loadFont() {
+ openFileExplorer(".json").then(files => {
+ const file = files[0]
+ const reader = new FileReader()
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result
+
+ // From lily's assets
+ // // Thank you PenguinMod for providing this code.
+
+ const targetId = runtime.getTargetForStage().id //util.target.id not working!
+ const assetName = Cast.toString(file.name)
+
+ const buffer = arrayBuffer
+
+ const storage = runtime.storage
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ )
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ )
+ alert("Font loaded successfully!")
+ } catch (e) {
+ console.error(e)
+ alert("Error loading font.")
+ }
+
+ // End of PenguinMod
+ }
+
+ reader.readAsArrayBuffer(file);
+ })
+ }
+ openConv() {{open("https://gero3.github.io/facetype.js/")}}
+
+ }
+ Scratch.extensions.register(new ThreeObjects())
+
+ class ThreeLights {
+ getInfo() {
+ return {
+ id: "threeLights",
+ name: "Three Lights",
+ color1: "#c7a22aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "addLight", blockType: Scratch.BlockType.COMMAND, text: "add light [NAME] type [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "lightTypes"}}},
+ {opcode: "setLight", blockType: Scratch.BlockType.COMMAND, text: "set light [NAME][PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lightProperties", defaultValue: "intensity"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1", exemptFromNormalization: true}}},
+ ],
+ menus: {
+ lightTypes: {acceptReporters: false, items: [
+ {text: "Ambient Light", value: "AmbientLight"},{text: "Directional Light", value: "DirectionalLight"},{text: "Point Light", value: "PointLight"},{text: "Hemisphere Light", value: "HemisphereLight"},{text: "Spot Light", value: "SpotLight"},
+ ]},
+ lightProperties: {acceptReporters: false, items: [
+ {text: "Color", value: "color"},{text: "Intensity", value: "intensity"},{text: "Cast Shadow?", value: "castShadow"},
+ {text: "Ground Color (HemisphereLight)", value: "groundColor"},
+ {text: "Map (SpotLight)", value: "map"},{text: "Distance (SpotLight)", value: "distance"},{text: "Decay (SpotLight)", value: "decay"},{text: "Penumbra (SpotLight)", value: "penumbra"},{text: "Angle/Size (SpotLight)", value: "angle"},{text: "Power (SpotLight)", value: "power"},
+ {text: "Target Position (Directional/SpotLight)", value: "target"},
+ ]},
+ }
+ }}
+
+ addLight(args) {
+ const light = new THREE[args.TYPE](0xffffff, 1)
+
+ createObject(args.NAME, light, args.GROUP)
+ lights[args.NAME] = light
+ if (light.type === "AmbientLight" || "HemisphereLight") return
+
+ light.castShadow = true
+ if (light.type === "PointLight") return
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0)
+ scene.add(light.target)
+
+ light.pos = new THREE.Vector3(0,0,0)
+
+ light.shadow.mapSize.width = 4096
+ light.shadow.mapSize.height = 2048
+
+ if (light.type === "SpotLight") {
+ light.decay = 0
+ light.shadow.camera.near = 500;
+ light.shadow.camera.far = 4000;
+ light.shadow.camera.fov = 30;
+ }
+ light.shadow.needsUpdate = true
+ light.needsUpdate = true
+ }
+
+ setLight(args) {
+ const light = lights[args.NAME]
+ if (!args.PROPERTY) return
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)) //vector3
+ light.target.updateMatrixWorld();
+ }
+ else {
+ light[args.PROPERTY] = getAsset(args.VALUE)
+ }
+ light.needsUpdate = true
+
+ if (light.type === "AmbientLight" || "HemisphereLight") return
+
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true
+ }
+
+ }
+ Scratch.extensions.register(new ThreeLights())
+
+ class ThreeUtilities {
+ getInfo() {
+ return {
+ id: "threeUtility",
+ name: "Three Utilities",
+ color1: "#3875c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "newVector2", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}}},
+ {opcode: "newVector3", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y] [Z]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}, Z: {type: Scratch.ArgumentType.NUMBER}}},
+ "---",
+ {opcode: "operateV3", blockType: Scratch.BlockType.REPORTER, text: "do [V3] [O] [V32]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, O: {type: Scratch.ArgumentType.STRING, menu: "operators"}, V32: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
+ {opcode: "moveVector3", blockType: Scratch.BlockType.REPORTER, text: "move [S] steps in vector [V3] in direction [D3]", arguments: {S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
+ {opcode: "directionTo", blockType: Scratch.BlockType.REPORTER, text: "direction from [V3] to [T3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, T3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
+ "---",
+ {opcode: "newColor", blockType: Scratch.BlockType.REPORTER, text: "New Color [HEX]", arguments: {HEX: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff"}}},
+ {opcode: "newFog", blockType: Scratch.BlockType.REPORTER, text: "New Fog [COLOR] [NEAR] [FAR]", arguments: {COLOR: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff", exemptFromNormalization: true}, NEAR: {type: Scratch.ArgumentType.NUMBER}, FAR: {type: Scratch.ArgumentType.NUMBER, defaultValue: 10}}},
+ {opcode: "newTexture", blockType: Scratch.BlockType.REPORTER, text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
+ {opcode: "newCubeTexture", blockType: Scratch.BlockType.REPORTER, text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]", arguments: {"COSTUMEX0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEX1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ1": {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
+ {opcode: "newEquirectangularTexture", blockType: Scratch.BlockType.REPORTER, text: "New Equirectangular Texture [COSTUME] [MODE]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"}}},
+ "---",
+ {opcode: "curve", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]", arguments: {TYPE: {type: Scratch.ArgumentType.STRING, menu: "curveTypes"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]"}, CLOSED: {type: Scratch.ArgumentType.STRING, defaultValue: "true"}}},
+ "---",
+ {opcode:"mouseDown",extensions: ["colours_sensing"], blockType: Scratch.BlockType.BOOLEAN, text: "mouse [BUTTON] [action]?", arguments: {BUTTON: {type: Scratch.ArgumentType.STRING, menu: "mouseButtons"},action: {type: Scratch.ArgumentType.STRING, menu: "mouseAction"}}},
+ {opcode: "mousePos",extensions: ["colours_sensing"], blockType: Scratch.BlockType.REPORTER, text: "mouse position", arguments: {}},
+ "---",
+ {opcode: "getItem",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "get item [ITEM] of [ARRAY]", arguments: {ITEM: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}, ARRAY: {type: Scratch.ArgumentType.STRING, defaultValue: `["myObject", "myLight"]`}}},
+ {blockType: Scratch.BlockType.LABEL, text: "↳ Raycasting"},
+ {opcode: "raycast", blockType: Scratch.BlockType.COMMAND, text: "Raycast from [V3] in direction [D3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,1]"}}},
+ {opcode: "getRaycast", blockType: Scratch.BlockType.REPORTER, text: "get raycast [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "raycastProperties"}}},
+
+ ],
+ menus: {
+ materialProperties: {acceptReporters: false, items: [
+ {text: "Color", value: "color"},{text: "Map (texture)", value: "map"},{text: "Alpha Map (texture)", value: "alphaMap"},{text: "Alpha Test (0-1)", value: "alphaTest"},{text: "Side (front/back/double)", value: "side"},{text: "Bump Map (texture)", value: "bumpMap"},{text: "Bump Scale", value: "bumpScale"},
+ ]},
+ textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
+ textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
+ raycastProperties: {acceptReporters: false, items: [
+ {text: "Intersected Object Names", value: "name"},{text: "Number of Objects", value: "number"},{text: "Intersected Objects distances", value: "distance"},
+ ]},
+ mouseButtons: {acceptReporters: false, items: ["left","middle","right"]},
+ mouseAction: {acceptReporters: false, items: ["Down","Clicked"]},
+ curveTypes: {acceptReporters: false, items: ["CatmullRomCurve3"]},
+ operators: {acceptReporters: false, items: [
+ "+","-","*","/","=","max","min","dot","cross","distance to","angle to","apply euler",
+ ]}
+ }
+ }}
+ mouseDown(args) {
+ if (args.action === "Down") return isMouseDown[args.BUTTON]
+ if (args.action === "Clicked") {
+ if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false
+ else prevMouse[args.BUTTON] = true; return true
+ }
+ }
+ mousePos(event) {
+ return JSON.stringify(mouseNDC)
+ }
+ newVector3(args) {
+ return JSON.stringify([args.X, args.Y, args.Z])
+ }
+ operateV3(args){
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3))
+ const v32 = new THREE.Vector3(...JSON.parse(args.V32))
+
+ let r
+ if (args.O == "+") r = v3.add(v32)
+ else if (args.O == "-") r = v3.sub(v32)
+ else if (args.O == "*") r = v3.multiply(v32)
+ else if (args.O == "/") r = v3.divide(v32)
+ else if (args.O == "=") r = v3.equals(v32)
+ else if (args.O == "max") r = v3.max(v32)
+ else if (args.O == "min") r = v3.min(v32)
+ else if (args.O == "dot") r = v3.dot(v32)
+ else if (args.O == "cross") r = v3.cross(v32)
+ else if (args.O == "distance to") r = v3.distanceTo(v32)
+ else if (args.O == "angle to") r = v3.angleTo(v32)
+ else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder))
+
+ if (typeof(r) == "object") return JSON.stringify([r.x, r.y, r.z])
+ else return JSON.stringify(r)
+ }
+
+ newVector2(args) {
+ return JSON.stringify([args.X, args.Y])
+ }
+
+ moveVector3(args) {
+ const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
+ const steps = Number(args.S);
+
+ const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
+
+ const yaw = THREE.MathUtils.degToRad(yawInputDeg);
+ const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
+ const roll = THREE.MathUtils.degToRad(rollInputDeg);
+
+ const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
+
+ const forwardVector = new THREE.Vector3(0, 0, -1);
+ const direction = forwardVector.applyEuler(euler).normalize();
+
+ const newPos = currentPos.add(direction.multiplyScalar(steps));
+ return JSON.stringify([newPos.x, newPos.y, newPos.z]);
+ }
+
+ directionTo(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3))
+ const toV3 = new THREE.Vector3(...JSON.parse(args.T3))
+
+ const direction = toV3.clone().sub(v3).normalize();
+ // Pitch (X)
+ const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x*direction.x + direction.z*direction.z));
+ // Yaw (Y)
+ const yaw = Math.atan2(direction.x, direction.z);
+
+ // Roll always 0
+ return JSON.stringify([180+THREE.MathUtils.radToDeg(pitch),THREE.MathUtils.radToDeg(yaw),0])
+ }
+
+ newColor(args) {
+ const color = new THREE.Color(args.HEX)
+ const uuid = crypto.randomUUID()
+ assets.colors[uuid] = color
+ return `colors/${uuid}`
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR)
+ const uuid = crypto.randomUUID()
+ assets.fogs[uuid] = fog
+ return `fogs/${uuid}`
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME)
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
+ assets.textures[texture.uuid] = texture
+ return `textures/${texture.uuid}`
+ }
+ async newCubeTexture(args) {
+ const uris = [encodeCostume(args.COSTUMEX0),encodeCostume(args.COSTUMEX1), encodeCostume(args.COSTUMEY0),encodeCostume(args.COSTUMEY1), encodeCostume(args.COSTUMEZ0),encodeCostume(args.COSTUMEZ1)]
+ const normalized = await Promise.all(uris.map(uri => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
+
+ texture.name = "CubeTexture" + args.COSTUMEX0;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
+ assets.textures[texture.uuid] = texture
+ return `textures/${texture.uuid}`
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME)
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME
+ texture.mapping = THREE.EquirectangularReflectionMapping
+
+ setTexutre(texture, args.MODE)
+ assets.textures[texture.uuid] = texture
+ return `textures/${texture.uuid}`
+ }
+
+ curve(args) {
+ function parsePoints(input) {
+ // Match all [x,y,z] groups
+ const matches = input.match(/\[([^\]]+)\]/g)
+ if (!matches) return []
+
+ return matches.map(str => {
+ const nums = str
+ .replace(/[\[\]\s]/g, '')
+ .split(',')
+ .map(Number)
+ return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0)
+ })
+ }
+ const points = parsePoints(args.POINTS)
+ const curve = new THREE[args.TYPE](points)
+ curve.closed = JSON.parse(args.CLOSED)
+
+ const uuid = crypto.randomUUID()
+ assets.curves[uuid] = curve
+ return `curves/${uuid}`
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY)
+ const item = items[args.ITEM - 1]
+ if (!item) return "0"
+ return item
+ }
+
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3))
+ // rotation is in degrees => convert to radians first
+ const rot = JSON.parse(args.D3).map(v => v * Math.PI / 180)
+
+ const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder)
+ const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize()
+
+ const raycaster = new THREE.Raycaster()
+ //const camera = getObject(args.CAMERA)
+ raycaster.set( origin, direction );
+
+ const intersects = raycaster.intersectObjects( scene.children, true )
+
+ raycastResult = intersects
+ }
+ getRaycast(args) {
+ if (args.PROPERTY === "number") return raycastResult.length
+ if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map(i => i.distance))
+ return JSON.stringify(raycastResult.map(i => i.object[args.PROPERTY]))
+ }
+
+ }
+ Scratch.extensions.register(new ThreeUtilities())
+
+ class ThreeGLB {
+ getInfo() {
+ return {
+ id: "threeGLB",
+ name: "Three GLB Loader",
+ color1: "#c53838ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {blockType: Scratch.BlockType.BUTTON, text: "Load GLB File", func: "loadModelFile"},
+ {opcode: "addModel", blockType: Scratch.BlockType.COMMAND, text: "add [ITEM] as [NAME] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},ITEM: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}}},
+ {opcode: "getModel", blockType: Scratch.BlockType.REPORTER, text: "get object [PROPERTY] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "modelProperties"}}},
+ {opcode: "playAnimation", blockType: Scratch.BlockType.COMMAND, text: "play animation [ANAME] of [NAME], [TIMES] times", arguments: {TIMES: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
+ {opcode: "pauseAnimation", blockType: Scratch.BlockType.COMMAND, text: "set [TOGGLE] animation [ANAME] of [NAME]", arguments: {TOGGLE: {type: Scratch.ArgumentType.NUMBER, menu: "pauseUn"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
+ {opcode: "stopAnimation", blockType: Scratch.BlockType.COMMAND, text: "stop animation [ANAME] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
+
+ ],
+ menus: {
+ modelProperties: {acceptReporters: false, items: [
+ {text: "Animations", value: "animations"},
+ ]},
+ pauseUn: {acceptReporters: true, items: [{text: "Pause", value: "true"},{text: "Unpasue", value: "false"},]},
+ modelsList: {acceptReporters: false, items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
+ if (models.length < 1) return [["Load a model!"]]
+
+ // @ts-ignore
+ return models.map( m => [m.name] )
+ }},
+ }
+ }}
+
+ async loadModelFile() {
+
+ openFileExplorer(".glb").then(files => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ { // From lily's assets
+
+ // Thank you PenguinMod for providing this code.
+ {
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ //const res = await Scratch.fetch(args.URL);
+ //const buffer = await res.arrayBuffer();
+ const buffer = arrayBuffer
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Model loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading model.");
+ }
+ }
+ // End of PenguinMod
+ }
+ };
+
+ reader.readAsArrayBuffer(file);
+ })
+
+ }
+ async addModel(args) {
+ const group = await getModel(args.ITEM, args.NAME)
+
+ createObject(args.NAME, group, args.GROUP)
+ }
+ getModel(args){
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString()
+ }
+
+ playAnimation(args) {
+ const model = models[args.NAME]
+ if (!model) {console.log("no model!"); return}
+
+ const action = model.actions[args.ANAME] //clones of models dont have a stored actions!
+ if (!action) {
+ console.log("no action!")
+ return
+ }
+
+ args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity)
+
+ action.reset()
+ .play()
+ }
+ stopAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.stop();
+ }
+ pauseAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.paused = args.TOGGLE
+ }
+
+ }
+ Scratch.extensions.register(new ThreeGLB())
+
+ class ThreeAddons {
+ getInfo() {
+ return {
+ id: "threeAddons",
+ name: "Three Addons",
+ color1: "#c538a2ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {blockType: Scratch.BlockType.LABEL, text: "Orbit Control"},
+ {opcode: "OrbitControl", blockType: Scratch.BlockType.COMMAND, text: "set addon Orbit Control [STATE]", arguments: {STATE: {type: Scratch.ArgumentType.STRING, menu: "onoff"},}},
+
+ {blockType: Scratch.BlockType.LABEL, text: "Post Processing"},
+ {opcode: "resetComposer", blockType: Scratch.BlockType.COMMAND, text: "reset composer"},
+ {opcode: "bloom", blockType: Scratch.BlockType.COMMAND, text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},I: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, T:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
+ {opcode: "godRays", blockType: Scratch.BlockType.COMMAND, text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}, DEC:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.95}, DENS:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},EXP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1},WEI:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.4},RES:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},SAMP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 64},}},
+ {opcode: "dots", blockType: Scratch.BlockType.COMMAND, text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, A: {type: Scratch.ArgumentType.ANGLE, defaultValue: 0},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
+ {opcode: "depth", blockType: Scratch.BlockType.COMMAND, text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]", arguments: {FD: {type: Scratch.ArgumentType.NUMBER, defaultValue: (3)},FL: {type: Scratch.ArgumentType.NUMBER, defaultValue: (0.001)},BS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 4},H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 240},OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}}},
+ "---",
+ {opcode: "custom", blockType: Scratch.BlockType.COMMAND, text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myShader"}, FRA: {type: Scratch.ArgumentType.STRING}, VER: {type: Scratch.ArgumentType.STRING}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}, OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
+ ],
+ menus: {
+ onoff: {acceptReporters: true, items: [{text: "enabled", value: "1"},{text: "disabled", value: "0"},]},
+ blendModes: {acceptReporters: false, items: [
+ "SKIP","SET","ADD","ALPHA","AVERAGE","COLOR","COLOR_BURN","COLOR_DODGE",
+ "DARKEN","DIFFERENCE","DIVIDE","DST","EXCLUSION","HARD_LIGHT","HARD_MIX",
+ "HUE","INVERT","INVERT_RGB","LIGHTEN","LINEAR_BURN","LINEAR_DODGE",
+ "LINEAR_LIGHT","LUMINOSITY","MULTIPLY","NEGATION","NORMAL","OVERLAY",
+ "PIN_LIGHT","REFLECT","SCREEN","SRC","SATURATION","SOFT_LIGHT","SUBTRACT",
+ "VIVID_LIGHT"
+ ]},
+ }
+ }}
+
+ OrbitControl(args) {
+ if (controls) controls.dispose()
+
+ console.log("creating...", OrbitControls)
+ controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
+ controls.enableDamping = true
+
+ controls.enabled = !!args.STATE
+ console.log(controls)
+ }
+
+ resetComposer() {
+ composer.passes = []
+ passes = {}
+ customEffects = []
+ updateComposers()
+ }
+
+ bloom(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ bloomEffect.blendMode.opacity.value = args.OP
+
+ const pass = new EffectPass(camera, bloomEffect)
+
+ composer.addPass(pass)
+ }
+
+ godRays(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ let object = getObject(args.NAME)
+ const sun = object
+
+ const godRays = new GodRaysEffect(camera, sun, {
+ resolutionScale: args.RES,
+ density: args.DENS, // ray density
+ decay: args.DEC, // fade out
+ weight: args.WEI, // brightness of rays
+ exposure: args.EXP,
+ samples: args.SAMP,
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ godRays.blendMode.opacity.value = args.OP
+ const pass = new EffectPass(camera, godRays)
+ composer.addPass(pass)
+ }
+
+ dots(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ dot.blendMode.opacity.value = args.OP
+ const pass = new EffectPass(camera, dot)
+ composer.addPass(pass)
+ }
+
+ depth(args) {
+ if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ })
+ dofEffect.blendMode.opacity.value = args.OP
+
+ const dofPass = new EffectPass(camera, dofEffect)
+ composer.addPass(dofPass)
+ }
+
+ async custom(args) {
+ function cleanGLSL(glslCode) {
+ //delete multilines comments
+ let cleanedCode = glslCode.replace(/\/\*[\s\S]*?\*\//g, ' ')
+ .replace(/ /g, '\n')
+ .replace(/\/\/.*$/gm, ' ')
+ .replace(/; /g, ';\n')
+
+ return cleanedCode;
+ }
+
+ let fs = cleanGLSL(`
+ ${args.FRA}
+ `)
+ if (!args.FRA.trim()) {fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`}
+ const vs = cleanGLSL(`
+ ${args.VER}
+ `)
+ console.log(fs)
+ console.log(vs)
+
+ const effect = new Effect(
+ "Custom",
+ fs,
+ {
+ blendFunction: BlendFunction[args.BLEND],
+ vertexShader: vs,
+ uniforms: new Map([ //uniforms usually in shaders... open to more!
+ ['time', new THREE.Uniform(0.0)],
+ ['resolution', new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height))]
+ ]),
+ defines: new Map([['USE_TIME', '1'], ['USE_VERTEX_TRANSFORM', '']]),
+ }
+ );
+
+ effect.blendMode.opacity.value = args.OP
+
+ const pass = new EffectPass(camera, effect);
+ composer.addPass(pass);
+
+ customEffects.push(effect);
+ }
+
+ }
+ Scratch.extensions.register(new ThreeAddons())
+
+ class RapierPhysics {
+ getInfo() {
+ return {
+ id: "rapierPhysics",
+ name: "RAPIER Physics",
+ color1: "#222222",
+ color2: "#203024ff",
+ color3: "#78f07eff",
+ blocks: [
+ {opcode: "createWorld", blockType: Scratch.BlockType.COMMAND, text: "create world | gravity:[G]", arguments: {G: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,-9.81,0]"}}},
+ {opcode: "getWorld", blockType: Scratch.BlockType.REPORTER, text: "get world [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "wProp"}}},
+ "---",
+ {opcode: "objectPhysics", blockType: Scratch.BlockType.COMMAND, text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]", arguments: {state2: {type: Scratch.ArgumentType.STRING, menu: "state2"},state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"}, type: {type: Scratch.ArgumentType.STRING, menu: "objectTypes", defaultValue: "dynamic"}, collider: {type: Scratch.ArgumentType.STRING, menu: "colliderTypes", defaultValue: "cuboid"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},mass: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},density: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},friction: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0.5"}}},
+ "---",
+ {blockType: Scratch.BlockType.LABEL, text: "- RigidBody"},
+ {opcode: "setRB", blockType: Scratch.BlockType.COMMAND, text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodySets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {opcode: "getRB", blockType: Scratch.BlockType.REPORTER, text: "get rigidbody [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodyProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "lockObjectAxis", blockType: Scratch.BlockType.COMMAND, text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]", arguments: {OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lockAxes"}, X: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Y: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Z: {type: Scratch.ArgumentType.STRING, menu: "tf"}}},
+ "---",
+ {opcode: "addForce", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,10,0]"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "forces", defaultValue: "addForce"},SPACE: {type: Scratch.ArgumentType.STRING, menu: "spaces", defaultValue: "world"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {opcode: "resetForces", blockType: Scratch.BlockType.COMMAND, text: "reset [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "resetF", defaultValue: "resetForces"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "enableCCD", blockType: Scratch.BlockType.COMMAND, text: "enable Continuous Collision Detection for [OBJECT] [state]", arguments: {state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "oPropS", defaultValue: "physics"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "fixedJoint", blockType: Scratch.BlockType.COMMAND, text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},RA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},RB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},}},
+ {opcode: "sphericalJoint", blockType: Scratch.BlockType.COMMAND, text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},}},
+ {opcode: "revoluteJoint", blockType: Scratch.BlockType.COMMAND, text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},X: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"},}},
+ "---",
+ {blockType: Scratch.BlockType.LABEL, text: "- Collider"},
+ {opcode: "setC", blockType: Scratch.BlockType.COMMAND, text: "set collider [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderSets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {opcode: "getC", blockType: Scratch.BlockType.REPORTER, text: "get collider [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ "---",
+ {opcode: "sensorSingle", blockType: Scratch.BlockType.BOOLEAN, text: "is sensor [SENSOR] touching [OBJECT]?", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
+ {opcode: "sensorAll", blockType: Scratch.BlockType.REPORTER, text: "objects touching sensor [SENSOR]", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}}}
+ ],
+ menus: {
+ wProp: {acceptReporters: false, items: [
+ {text: "Gravity", value: "gravity"}, {text: "log to console", value: "log"}
+ ]},
+ tf: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true", value: "true"}]},
+ lockAxes: {acceptReporters: false, items: [
+ {text: "Translation", value: "setEnabledTranslations"}, {text: "Rotation", value: "setEnabledRotations"}
+ ]},
+ rigidBodyProperties: {acceptReporters: false, items: [
+ {text: "Type", value: "bodyType"},
+ {text: "Linear Velocity", value: "linvel"},
+ {text: "Angular Velocity", value: "angvel"},
+ {text: "Translation (position)", value: "translation"},
+ {text: "Rotation (quaternion)", value: "rotation"},
+ {text: "Mass", value: "mass"},
+ //{text: "Center of Mass", value: "centerOfMass"},
+ {text: "Linear Damping", value: "linearDamping"},
+ {text: "Angular Damping", value: "angularDamping"},
+ {text: "Is Sleeping?", value: "isSleeping"},
+ //{text: "Can Sleep?", value: "isCanSleep"},
+ {text: "Gravity Scale", value: "gravityScale"},
+ {text: "Is Fixed?", value: "isFixed"},
+ {text: "Is Dynamic?", value: "isDynamic"},
+ {text: "Is Kinematic?", value: "isKinematic"},
+ //{text: "Sleeping", value: "sleeping"}
+ ]},
+ rigidBodySets: {acceptReporters: false, items: [
+ //{text: "Linear Velocity", value: "setLinvel"},
+ //{text: "Angular Velocity", value: "setAngvel"},
+ //{text: "Mass", value: "setMass"},
+ {text: "Gravity Scale", value: "setGravityScale"},
+ //{text: "Can Sleep?", value: "setCanSleep"},
+ //{text: "Sleeping", value: "sleeping"},
+ {text: "Linear Damping", value: "setLinearDamping"},
+ {text: "Angular Damping", value: "setAngularDamping"},
+ {text: "Is Fixed?", value: "isFixed"},
+ {text: "Is Dynamic?", value: "isDynamic"},
+ {text: "Is Kinematic?", value: "isKinematic"}
+ ]},
+ colliderProperties: {acceptReporters: false, items: [
+ //{text: "Collider Type", value: "type"},
+ {text: "Is Sensor?", value: "isSensor"},
+ {text: "Friction", value: "friction"},
+ {text: "Restitution", value: "restitution"},
+ {text: "Density", value: "density"},
+ {text: "Mass", value: "mass"},
+ {text: "Position", value: "translation"},
+ {text: "Rotation", value: "rotation"},
+ //{text: "Area", value: "area"},
+ {text: "Volume", value: "volume"},
+ {text: "Collision Groups", value: "collisionGroups"},
+ //{text: "Collision Mask", value: "collisionMask"},
+ //{text: "Is Enabled?", value: "enabled"},
+ //{text: "Contact Count", value: "contactCount"},
+ //{text: "RigidBody Handle", value: "rigidBody"}
+ ]},
+ colliderSets: {acceptReporters: false, items: [
+ {text: "Friction", value: "setFriction"},
+ {text: "Restitution", value: "setRestitution"},
+ {text: "Density", value: "setDensity"},
+ {text: "Is Sensor?", value: "setSensor"},
+ {text: "Collision Groups", value: "setCollisionGroups"},
+ //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
+ //{text: "Position Offset", value: "setTranslation"},
+ //{text: "Rotation Offset", value: "setRotation"}
+ ]},
+ state: {acceptReporters: true, items: [{text: "on", value: "true"},{text: "off", value: "false"}]},
+ state2: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true (must be fixed)", value: "true"}]},
+ spaces: {acceptReporters: false, items: [{text: "World", value: "world"},{text: "Local", value: "local"}]},
+ objectTypes: {acceptReporters: false, items: [{text: "Dynamic", value: "dynamic"},{text: "Fixed", value: "fixed"},{text: "Kinematic Position Based",value: "kinematicPositionBased"}]},
+ colliderTypes: {acceptReporters: false, items: [{text: "Box, Rectangle, cuboid", value: "cuboid"},{text: "Sphere, ball", value: "ball"},{text: "Custom, complex simple shapes, convexHull", value: "convexHull"},{text:"Precision, TriMesh",value:"trimesh"}]},
+ forces: {acceptReporters: false, items: [{text: "Force", value: "addForce"},{text: "Torque (rotation)", value: "addTorque"},{text: "Apply Impulse", value: "applyImpulse"},{text: "Apply Torque Impulse (rotation)", value: "applyTorqueImpulse"},{text: "Linear Velocity", value: "setLinvel"},{text: "Angular Velocity", value: "setAngvel"},]},
+ resetF: {acceptReporters: false, items: [{text:"Forces", value: "resetForces"},{text:"Torques", value: "resetTorques"},]}
+ }
+ }
+ }
+ joint(jointData, bodyA, bodyB) {
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true)
+ }
+
+ fixedJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+ let RA = JSON.parse(args.RA).map(Number)
+ let RB = JSON.parse(args.RB).map(Number)
+
+ RA = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RA[0]),
+ THREE.MathUtils.degToRad(RA[1]),
+ THREE.MathUtils.degToRad(RA[2])
+ )
+ )
+ RB = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RB[0]),
+ THREE.MathUtils.degToRad(RB[1]),
+ THREE.MathUtils.degToRad(RB[2])
+ )
+ )
+
+ const data = RAPIER.JointData.fixed(
+ { x: VA[0], y: VA[1], z: VA[2] }, RA,
+ { x: VB[0], y: VB[1], z: VB[2] }, RB
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ sphericalJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+
+ const data = RAPIER.JointData.spherical(
+ { x: VA[0], y: VA[1], z: VA[2] },
+ { x: VB[0], y: VB[1], z: VB[2] }
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ revoluteJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+ const x = JSON.parse(args.X).map(Number)
+
+ const data = RAPIER.JointData.revolute(
+ { x: VA[0], y: VA[1], z: VA[2] },
+ { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ prismaticJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number)
+ const VB = JSON.parse(args.VB).map(Number)
+ const x = JSON.parse(args.X).map(Number)
+
+ const data = RAPIER.JointData.prismatic(
+ { x: VA[0], y: VA[1], z: VA[2] },
+ { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
+ )
+ const objectA = getObject(args.ObjA)
+ let object = getObject(args.ObjB)
+ this.joint(data, objectA, object)
+ }
+
+ createWorld(args) {
+ const v3 = JSON.parse(args.G).map(Number)
+ const gravity = { x: v3[0], y: v3[1], z: v3[2]}
+ physicsWorld = new RAPIER.World(gravity)
+
+ console.log(physicsWorld)
+ }
+
+ getWorld(args) {
+ if (args.PROPERTY === "log") {console.log(physicsWorld); return "logged"}
+ return JSON.stringify(physicsWorld[args.PROPERTY])
+ }
+
+ setRB(args) {
+ let value = args.VALUE
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
+ let object = getObject(args.OBJECT)
+ object.rigidBody[args.PROPERTY](value)
+ }
+ setC(args) {
+ let value = args.VALUE
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
+ let object = getObject(args.OBJECT)
+ object.collider[args.PROPERTY](value)
+ }
+
+ getRB(args) {
+ let object = getObject(args.OBJECT)
+ return JSON.stringify(object.rigidBody[args.PROPERTY]())
+ }
+ getC(args) {
+ let object = getObject(args.OBJECT)
+ return JSON.stringify(object.collider[args.PROPERTY]())
+ }
+
+ lockObjectAxis(args) {
+ let object = getObject(args.OBJECT)
+ const x = !JSON.parse(args.X)
+ const y = !JSON.parse(args.Y)
+ const z = !JSON.parse(args.Z)
+ object.rigidBody[args.PROPERTY](x,y,z,true) //changes is xyz, wake up
+ }
+
+ objectPhysics(args) {
+ let object = getObject(args.OBJECT)
+ object.physics = JSON.parse(args.state)
+
+ if (JSON.parse(args.state)) {
+ //if already exists delete:
+ if (object.rigidBody) {
+ physicsWorld.removeRigidBody(object.rigidBody)
+ object.rigidBody = null
+ object.collider = null
+ }
+ /*asing a rigidbody and collider to object and add them to physicsWorld*/
+ let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
+ .setTranslation(object.position.x, object.position.y, object.position.z)
+ .setRotation({w: object.quaternion._w, x: object.quaternion._x, y: object.quaternion._y, z: object.quaternion._z})
+
+ let colliderDesc
+ switch(args.collider) {
+ case "cuboid": colliderDesc = createCuboidCollider(object,); break
+ case "ball": colliderDesc = createBallCollider(object); break
+ case "convexHull": colliderDesc = createConvexHullCollider(object); break
+ case "trimesh": colliderDesc = TriMesh(object); break
+ }
+ colliderDesc.setSensor(JSON.parse(args.state2)).setMass(args.mass).setDensity(args.density).setFriction(args.friction)
+
+ let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc)
+ let collider = physicsWorld.createCollider(colliderDesc, rigidBody)
+
+ object.rigidBody = rigidBody
+ object.collider = collider
+ } else {
+ /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
+ physicsWorld.removeRigidBody(object.rigidBody)
+ object.rigidBody = null
+ object.collider = null
+ }
+
+ }
+
+ enableCCD(args) {
+ let object = getObject(args.OBJECT)
+ if (object.physics) {
+ let rigidBody = object.rigidBody
+ rigidBody.enableCcd(JSON.parse(args.state))
+ }
+ }
+
+ addForce(args) {
+ let object = getObject(args.OBJECT)
+ const vector = JSON.parse(args.VALUE).map(Number)
+
+ let force = new THREE.Vector3(vector[0],vector[1],vector[2])
+ if (args.SPACE === "local") {
+ force.applyQuaternion(object.quaternion);
+ }
+
+ object.rigidBody[args.PROPERTY](force,true)
+ }
+
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true)
+ }
+
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR)
+
+ let object = getObject(args.OBJECT)
+
+ let touching = false
+ physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
+ if (otherCollider === object.collider) touching = true
+ })
+
+ return touching
+ }
+
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR)
+
+ const touchedObjects = []
+
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
+ // find owner of collider
+ const otherObject = scene.children.find(o => o.collider === otherCollider)
+ console.log(otherCollider)
+ if (otherObject) touchedObjects.push(otherObject.name)
+ })
+
+ return JSON.stringify(touchedObjects)
+ }
+
+ }
+ Scratch.extensions.register(new RapierPhysics())
+
+ //Thanks to the PointerLock extension of Turbowarp
+ const mouse = vm.runtime.ioDevices.mouse;
+ let isLocked = false;
+ let isPointerLockEnabled = false;
+
+ let rect = threeRenderer.domElement.getBoundingClientRect();
+ document.addEventListener("resize", () => {
+ rect = threeRenderer.domElement.getBoundingClientRect();
+ });
+
+ const postMouseData = (e, isDown) => {
+ const { movementX, movementY } = e;
+ const { width, height } = rect;
+ const x = mouse._clientX + movementX;
+ const y = mouse._clientY - movementY;
+ mouse._clientX = x;
+ mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
+ mouse._clientY = y;
+ mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
+ if (typeof isDown === "boolean") {
+ const data = {
+ button: e.button,
+ isDown,
+ };
+ originalPostIOData(data);
+ }
+ };
+
+ const mouseDevice = vm.runtime.ioDevices.mouse;
+ const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
+ mouseDevice.postData = (data) => {
+ if (!isPointerLockEnabled) {
+ return originalPostIOData(data);
+ }
+ };
+
+ document.addEventListener(
+ "mousedown",
+ (e) => {
+ // @ts-expect-error
+ if (threeRenderer.domElement.contains(e.target)) {
+ if (isLocked) {
+ postMouseData(e, true);
+ } else if (isPointerLockEnabled) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mouseup",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e, false);
+ // @ts-expect-error
+ } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mousemove",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e);
+ }
+ },
+ true
+ );
+
+ document.addEventListener("pointerlockchange", () => {
+ isLocked = document.pointerLockElement === threeRenderer.domElement;
+ });
+ document.addEventListener("pointerlockerror", (e) => {
+ console.error("Pointer lock error", e);
+ });
+
+ const oldStep = vm.runtime._step;
+ vm.runtime._step = function (...args) {
+ const ret = oldStep.call(this, ...args);
+ if (isPointerLockEnabled) {
+ const { width, height } = rect;
+ mouse._clientX = width / 2;
+ mouse._clientY = height / 2;
+ mouse._scratchX = 0;
+ mouse._scratchY = 0;
+ }
+ return ret;
+ };
+
+ vm.runtime.on("PROJECT_LOADED", () => {
+ isPointerLockEnabled = false;
+ if (isLocked) {
+ document.exitPointerLock();
+ }
+ });
+
+ class Pointerlock {
+ getInfo() {
+ return {
+ id: "threepointerlockmod",
+ name: "Pointerlock for Extra 3D",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [
+ {opcode: "setLocked", blockType: Scratch.BlockType.COMMAND, text: "set pointer lock [enabled]", arguments: { enabled: { type: Scratch.ArgumentType.STRING, defaultValue: "true", menu: "enabled"}},},
+ {opcode: "isLocked", blockType: Scratch.BlockType.BOOLEAN, text: "pointer locked?",},
+ ],
+ menus: {
+ enabled: {acceptReporters: true, items: [
+ {text: "enabled", value: "true"},{text: "disabled", value: "false"},
+ ]}
+ },
+ }
+ }
+
+ setLocked(args) {
+ isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
+ if (!isPointerLockEnabled && isLocked) {
+ document.exitPointerLock();
+ }
+ }
+
+ isLocked() {
+ return isLocked;
+ }
+ }
+Scratch.extensions.register(new Pointerlock())
+
+ })
+
+
+
+
+})(Scratch);
diff --git a/threejsD_REMOTE_13852.js b/threejsD_REMOTE_13852.js
new file mode 100644
index 0000000..447584f
--- /dev/null
+++ b/threejsD_REMOTE_13852.js
@@ -0,0 +1,5016 @@
+/* jshint esversion: 11 */
+// Name: Extra 3D
+// ID: threejsExtension
+// Description: Use three js inside Turbowarp! A 3D graphics library.
+// By: Civero
+// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
+
+(function(Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Three-D extension must run unsandboxed");
+ }
+
+ if (Scratch.vm.runtime.isPackaged) {
+ alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
+ return;
+ }
+ //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
+
+ const vm = Scratch.vm;
+ const runtime = vm.runtime;
+ const renderer = Scratch.renderer;
+ const canvas = renderer.canvas;
+ const Cast = Scratch.Cast;
+ const menuIconURI =
+ "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
+
+ let alerts = false;
+ console.log("alerts are " + (alerts ? "enabled" : "disabled"));
+
+ let isMouseDown = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+ let prevMouse = {
+ left: false,
+ middle: false,
+ right: false,
+ };
+
+ let lastWidth = 0;
+ let lastHeight = 0;
+
+ let THREE;
+ let clock;
+ let running;
+ let loopId;
+ //Addons
+ let GLTFLoader;
+ let gltf;
+ let OrbitControls;
+ let controls;
+ let BufferGeometryUtils;
+ let TextGeometry;
+ let fontLoad;
+ //Physics
+ let RAPIER;
+ let physicsWorld;
+
+ let threeRenderer;
+ let scene;
+ let camera;
+ let eulerOrder = "YXZ";
+
+ let composer;
+ let passes = {};
+ let customEffects = [];
+ let renderTargets = {};
+
+ let materials = {};
+ let geometries = {};
+ let lights = {};
+ let models = {};
+
+ let assets = {
+ //should i place materials, geometries; inside too?
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {}, //not the same as the global one! this one only stores textures
+ };
+
+ let raycastResult = [];
+
+ function resetor(level) {
+ camera = undefined;
+ composer.reset();
+
+ passes = {};
+ customEffects = [];
+ renderTargets = {};
+
+ materials = {};
+ geometries = {};
+ lights = {};
+ models = {};
+
+ if (level > 0) {
+ assets = {
+ textures: {},
+ colors: {},
+ fogs: {},
+ curves: {},
+ renderTargets: {},
+ };
+ }
+
+ updateComposers();
+ }
+
+ //utility
+ function vector3ToString(prop) {
+ if (!prop) return "0,0,0";
+
+ const x =
+ typeof prop.x === "number" ?
+ prop.x :
+ typeof prop._x === "number" ?
+ prop._x :
+ JSON.stringify(prop).includes("X") ?
+ prop :
+ 0;
+ const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
+ const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
+
+ return [x, y, z];
+ }
+
+ //objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true);
+ if (object) {
+ removeObject(name);
+ alerts ? alert(name + " already exsisted, will replace!") : null;
+ }
+ content.name = name;
+ content.rotation._order = eulerOrder;
+ parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+ content.physics = false;
+
+ object.add(content);
+ }
+
+ function removeObject(name) {
+ let object = getObject(name);
+ if (!object) return;
+
+ scene.remove(object);
+
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true);
+ physicsWorld.removeRigidBody(object.rigidBody, true);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ if (object.isLight) {
+ delete lights[name];
+ }
+ }
+
+ function getObject(name, isNew) {
+ let object = null;
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
+ return;
+ }
+ object = scene.getObjectByName(name);
+ if (!object && !isNew) {
+ alerts ? alert(name + " does not exist! Add it to scene") : null;
+ return;
+ }
+ return object;
+ }
+
+ //materials
+ function encodeCostume(name) {
+ if (name.startsWith("data:image/")) return name;
+ return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ }
+
+ function setTexutre(texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace;
+
+ if (mode === "Pixelate") {
+ texture.minFilter = THREE.NearestFilter;
+ texture.magFilter = THREE.NearestFilter;
+ } else {
+ //Blur
+ texture.minFilter = THREE.NearestMipmapLinearFilter;
+ texture.magFilter = THREE.NearestMipmapLinearFilter;
+ }
+
+ if (style === "Repeat") {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(x, y);
+ }
+
+ texture.generateMipmaps = true;
+ }
+ async function resizeImageToSquare(uri, size = 256) {
+ return new Promise((resolve) => {
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = size;
+ canvas.height = size;
+ const ctx = canvas.getContext("2d");
+
+ // clear + draw image scaled to fit canvas
+ ctx.clearRect(0, 0, size, size);
+ ctx.drawImage(img, 0, 0, size, size);
+
+ resolve(canvas.toDataURL()); // return normalized Data URI
+ //delete canvas?
+ };
+ img.src = uri;
+ });
+ }
+ //light
+ function updateShadowFrustum(light, focusPos) {
+ if (light.type !== "DirectionalLight") return;
+
+ // Frustum Size - Increase this value to cover a larger area.
+ const d = 50;
+
+ // Update Orthographic Shadow Camera Frustum
+ const shadowCamera = light.shadow.camera;
+
+ // Set the width/height of the frustum
+ shadowCamera.left = -d;
+ shadowCamera.right = d;
+ shadowCamera.top = d;
+ shadowCamera.bottom = -d;
+
+ // Determine ranges
+ shadowCamera.near = 0.1;
+ shadowCamera.far = 500;
+
+ // Position the Light and its Target
+ light.target.position.copy(focusPos);
+ const direction = light.position.clone().sub(light.target.position).normalize();
+ light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
+
+ // Ensure matrices are updated.
+ light.target.updateMatrixWorld();
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
+ //composer
+ function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
+
+ // always recreate the RenderPass to point to the current scene/camera
+ passes["Render"] = new RenderPass(scene, camera);
+
+ // ensure composer has a RenderPass as the first pass
+ const hasRender = composer.passes.some((p) => p && p.scene);
+ if (!hasRender) composer.addPass(passes["Render"]);
+ else {
+ // if composer already has one, replace it so it references current scene/camera
+ const idx = composer.passes.findIndex((p) => p && p.scene);
+ composer.passes[idx] = passes["Render"];
+ }
+ }
+ //utility
+ function getMouseNDC(event) {
+ // Use threeRenderer.domElement for correct offset
+ const rect = threeRenderer.domElement.getBoundingClientRect();
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
+ return [x, y];
+ }
+
+ function checkCanvasSize() {
+ const {
+ width,
+ height
+ } = canvas;
+ if (width !== lastWidth || height !== lastHeight) {
+ lastWidth = width;
+ lastHeight = height;
+ resize();
+ }
+ requestAnimationFrame(checkCanvasSize); //rerun next frame
+ }
+ //physics
+ function computeWorldBoundingBox(mesh) {
+ // Create a Box3 in world coordinates
+ const box = new THREE.Box3().setFromObject(mesh);
+ const size = new THREE.Vector3();
+ box.getSize(size);
+ const center = new THREE.Vector3();
+ box.getCenter(center);
+ return {
+ size,
+ center,
+ };
+ }
+
+ function createCuboidCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
+ return collider;
+ }
+
+ function createBallCollider(mesh) {
+ const {
+ size
+ } = computeWorldBoundingBox(mesh);
+ // radius = 1/2 of the largest verticie
+ const radius = Math.max(size.x, size.y, size.z) / 2;
+ const collider = RAPIER.ColliderDesc.ball(radius);
+ return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
+ }
+
+ function createConvexHullCollider(mesh) {
+ mesh.updateWorldMatrix(true, false);
+
+ const position = mesh.geometry.attributes.position;
+ const vertices = [];
+ const vertex = new THREE.Vector3();
+
+ // Matrix for scale only
+ const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
+
+ for (let i = 0; i < position.count; i++) {
+ vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
+ vertices.push(vertex.x, vertex.y, vertex.z);
+ }
+
+ const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
+ return collider;
+ }
+
+ function TriMesh(mesh) {
+ // Get the positions array (from your geoPoints function)
+ const positions = mesh.geometry.attributes.position.array;
+ const numVertices = positions.length / 3;
+
+ // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
+ const indices = Array.from({
+ length: numVertices,
+ },
+ (_, i) => i
+ );
+
+ const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
+
+ return collider;
+ }
+
+ function getModel(model, name) {
+ const file = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === model);
+ if (!file) return;
+
+ return new Promise((resolve, reject) => {
+ gltf.parse(
+ file.asset.data.buffer,
+ "",
+ (gltf) => {
+ const root = gltf.scene;
+ root.traverse((child) => {
+ if (child.isMesh) {
+ child.castShadow = true;
+ child.receiveShadow = true;
+ }
+ });
+
+ const mixer = new THREE.AnimationMixer(root);
+ const actions = {};
+ gltf.animations.forEach((clip) => {
+ const act = mixer.clipAction(clip);
+ act.clampWhenFinished = true;
+ actions[clip.name] = act;
+ });
+
+ models[name] = {
+ root,
+ mixer,
+ actions,
+ };
+ resolve(root);
+ },
+ (error) => {
+ console.error("Error parsing GLB model:", error);
+ reject(error);
+ }
+ );
+ });
+ }
+ async function openFileExplorer(format) {
+ return new Promise((resolve) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = format;
+ input.multiple = false;
+ input.onchange = () => {
+ resolve(input.files);
+ input.remove();
+ };
+ input.click();
+ });
+ }
+
+ function getMeshesUsingTexture(scene, targetTexture) {
+ const meshes = [];
+
+ scene.traverse((object) => {
+ if (object.material) {
+ const materials = Array.isArray(object.material) ? object.material : [object.material];
+ for (const material of materials) {
+ if (material.map === targetTexture) {
+ meshes.push(object);
+ break;
+ }
+ }
+ }
+ });
+
+ return meshes;
+ }
+
+ function getAsset(path) {
+ if (typeof path == "string") {
+ //string?
+ if (path.includes("/")) {
+ //has the /?
+ const value = path.split("/");
+ console.log(value[0], value[1]);
+ return assets[value[0]][value[1]];
+ }
+ }
+
+ return JSON.parse(path); //boolean or number
+ }
+
+ let mouseNDC = [0, 0];
+ //loops/init
+ function stopLoop() {
+ if (!running) return;
+ running = false;
+
+ if (loopId) {
+ cancelAnimationFrame(loopId);
+ loopId = null;
+ if (threeRenderer) threeRenderer.clear();
+ }
+ }
+ async function load() {
+ if (!THREE) {
+ // @ts-ignore
+ THREE = await import("https://esm.sh/three@0.180.0");
+ //Addons
+ GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
+ OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
+ BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
+ TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
+ const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
+ fontLoad = new FontLoader.FontLoader();
+
+ const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
+ const {
+ EffectComposer,
+ EffectPass,
+ RenderPass,
+
+ Effect,
+ BloomEffect,
+ GodRaysEffect,
+ DotScreenEffect,
+ DepthOfFieldEffect,
+
+ BlendFunction,
+ } = POSTPROCESSING;
+ //so i can use them later as global
+ window.EffectComposer = EffectComposer;
+ window.EffectPass = EffectPass;
+ window.RenderPass = RenderPass;
+ window.Effect = Effect;
+ window.BloomEffect = BloomEffect;
+ window.GodRaysEffect = GodRaysEffect;
+ window.DotScreenEffect = DotScreenEffect;
+ window.DepthOfFieldEffect = DepthOfFieldEffect;
+ window.BlendFunction = BlendFunction;
+
+ RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
+ await RAPIER.init();
+
+ threeRenderer = new THREE.WebGLRenderer({
+ powerPreference: "high-performance",
+ antialias: false,
+ stencil: false,
+ depth: true,
+ });
+ threeRenderer.setPixelRatio(window.devicePixelRatio);
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
+ //threeRenderer.toneMappingExposure = 1.0 //(test)
+
+ threeRenderer.shadowMap.enabled = true;
+ threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
+ threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
+
+ gltf = new GLTFLoader.GLTFLoader();
+ clock = new THREE.Clock();
+
+ // Example: create a composer
+ composer = new EffectComposer(threeRenderer, {
+ frameBufferType: THREE.HalfFloatType,
+ });
+
+ renderer.addOverlay(threeRenderer.domElement, "manual");
+ renderer.addOverlay(canvas, "manual");
+ renderer.setBackgroundColor(1, 1, 1, 0);
+
+ resize();
+
+ window.addEventListener("mousedown", (e) => {
+ if (e.button === 0) isMouseDown.left = true;
+ if (e.button === 1) isMouseDown.middle = true;
+ if (e.button === 2) isMouseDown.right = true;
+ });
+ window.addEventListener("mouseup", (e) => {
+ if (e.button === 0) isMouseDown.left = false;
+ prevMouse.left = false;
+ if (e.button === 1) isMouseDown.middle = false;
+ prevMouse.middle = false;
+ if (e.button === 2) isMouseDown.right = false;
+ prevMouse.right = false;
+ });
+ // prevent contextmenu on right click
+ threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
+
+ threeRenderer.domElement.addEventListener("mousemove", (event) => {
+ mouseNDC = getMouseNDC(event);
+ });
+
+ running = false;
+ load();
+
+ startRenderLoop();
+ runtime.on("PROJECT_START", () => startRenderLoop());
+ runtime.on("PROJECT_STOP_ALL", () => stopLoop());
+ runtime.on("STAGE_SIZE_CHANGED", () => {
+ requestAnimationFrame(() => resize());
+ });
+ //if (!runtime.isPackaged) checkCanvasSize() //only in editor
+ }
+ }
+
+ function startRenderLoop() {
+ if (running) return;
+ running = true;
+
+ const loop = () => {
+ if (!running) return;
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step();
+
+ scene.children.forEach((obj) => {
+ if (!obj.isMesh || !obj.physics) return;
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation());
+ obj.quaternion.copy(obj.rigidBody.rotation());
+ }
+ });
+ }
+ if (scene && camera) {
+ if (controls) controls.update();
+
+ const delta = clock.getDelta();
+ Object.values(models).forEach((model) => {
+ if (model) model.mixer.update(delta);
+ });
+
+ Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
+
+ //update custom effects time
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("time")) {
+ e.uniforms.get("time").value += delta;
+ }
+ });
+ Object.values(renderTargets).forEach((t) => {
+ if (t.camera.type == "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height;
+ t.camera.updateProjectionMatrix();
+ }
+ // get meshes using the texture associated with this target
+ const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = false;
+ });
+
+ if (t.camera.type == "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target);
+ threeRenderer.clear(true, true, true);
+ threeRenderer.render(scene, t.camera);
+ } else {
+ t.target.clear(threeRenderer);
+ t.camera.update(threeRenderer, scene); //cubeCamera
+ }
+
+ displayMeshes.forEach((mesh) => {
+ mesh.visible = true;
+ });
+ });
+
+ camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
+ camera.updateProjectionMatrix();
+ threeRenderer.setRenderTarget(null);
+ composer.render(delta);
+ }
+
+ loopId = requestAnimationFrame(loop);
+ };
+
+ loopId = requestAnimationFrame(loop);
+ }
+
+ function resize() {
+ const w = canvas.width;
+ const h = canvas.height;
+
+ threeRenderer.setSize(w, h);
+ composer.setSize(w, h);
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("resolution")) {
+ e.uniforms.get("resolution").value.set(w, h);
+ }
+ });
+
+ if (camera) {
+ camera.aspect = w / h;
+ camera.updateProjectionMatrix();
+ }
+ }
+ //wait until all packages are loaded
+ Promise.resolve(load()).then(() => {
+ class threejsExtension {
+ getInfo() {
+ return {
+ id: "threejsExtension",
+ name: "Extra 3D",
+ color1: "#222222",
+ color2: "#222222",
+ color3: "#11cc99",
+ menuIconURI,
+ blockIconURI: menuIconURI,
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Show Docs",
+ func: "openDocs",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Toggle Alerts",
+ func: "alerts",
+ },
+ ],
+ menus: {},
+ };
+ }
+ openDocs() {
+ open("https://civ3ro.github.io/extensions/Documentation/");
+ }
+ alerts() {
+ alerts = !alerts;
+ alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
+ }
+ }
+ Scratch.extensions.register(new threejsExtension());
+
+ class ThreeRenderer {
+ getInfo() {
+ return {
+ id: "threeRenderer",
+ name: "Three Renderer",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setRendererRatio",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Pixel Ratio to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "eulerOrder",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set euler order to [VALUE]",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "YXZ",
+ },
+ },
+ },
+ ],
+ menus: {},
+ };
+ }
+
+ setRendererRatio(args) {
+ threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
+ }
+ eulerOrder(args) {
+ eulerOrder = args.VALUE;
+ console.log("euler order set to", eulerOrder);
+ }
+ }
+ Scratch.extensions.register(new ThreeRenderer());
+
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
+
+ getInfo() {
+ return {
+ id: "threeScene",
+ name: "Three Scene",
+ color1: "#4638c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newScene",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new Scene [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ },
+ },
+
+ {
+ opcode: "setSceneProperty",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set Scene [PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneProperties",
+ defaultValue: "background",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "getSceneObjects",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get Scene [THING]",
+ arguments: {
+ THING: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "sceneThings",
+ },
+ },
+ },
+ {
+ opcode: "reset",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Reset Everything",
+ },
+ ],
+ menus: {
+ sceneProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Background",
+ value: "background",
+ },
+ {
+ text: "Background Blurriness",
+ value: "backgroundBlurriness",
+ },
+ {
+ text: "Background Intensity",
+ value: "backgroundIntensity",
+ },
+ {
+ text: "Background Rotation",
+ value: "backgroundRotation",
+ },
+ {
+ text: "Environment",
+ value: "environment",
+ },
+ {
+ text: "Environment Intensity",
+ value: "environmentIntensity",
+ },
+ {
+ text: "Environment Rotation",
+ value: "environmentRotation",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ ],
+ },
+ sceneThings: {
+ acceptReporters: false,
+ items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
+ },
+ },
+ };
+ }
+
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME;
+ scene.background = new THREE.Color("#222");
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {
+ ...this.scenes,
+ ...scene,
+ };
+ resetor(0);
+ }
+
+ reset() {
+ resetor(1);
+ }
+
+ async setSceneProperty(args) {
+ const property = args.PROPERTY;
+ const value = getAsset(args.VALUE);
+
+ scene[property] = value;
+ }
+ getSceneObjects(args) {
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse((obj) => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ else if (args.THING === "Scene Properties") {
+ console.log(scene);
+ return "check console";
+ } else if (args.THING === "Other assets") return JSON.stringify(assets);
+
+ return JSON.stringify(names); // if objects
+ }
+ }
+ Scratch.extensions.register(new ThreeScene());
+
+ class ThreeCameras {
+ getInfo() {
+ return {
+ id: "threeCameras",
+ name: "Three Cameras",
+ color1: "#38c59bff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add camera [TYPE] [CAMERA] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraTypes",
+ },
+ },
+ },
+ {
+ opcode: "setCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "0.1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "getCamera",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get camera [PROPERTY] of [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "cameraProperties",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "renderSceneCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rendering camera to [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "cubeCamera",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "cubeCamera",
+ },
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "renderTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set a RenderTarget: [RT] for camera [CAMERA]",
+ arguments: {
+ CAMERA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myCamera",
+ },
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "sizeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set RenderTarget [RT] size to [W] [H]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ W: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 480,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 360,
+ },
+ },
+ },
+ {
+ opcode: "getTarget",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get RenderTarget: [RT] texture",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ {
+ opcode: "removeTarget",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove RenderTarget: [RT]",
+ arguments: {
+ RT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myTarget",
+ },
+ },
+ },
+ ],
+ menus: {
+ cameraTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Perspective",
+ value: "PerspectiveCamera",
+ }, ],
+ },
+ cameraProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Near",
+ value: "near",
+ },
+ {
+ text: "Far",
+ value: "far",
+ },
+ {
+ text: "FOV",
+ value: "fov",
+ },
+ {
+ text: "Focus (nothing...)",
+ value: "focus",
+ },
+ {
+ text: "Zoom",
+ value: "zoom",
+ },
+ ],
+ },
+ },
+ };
+ }
+ addCamera(args) {
+ let v2 = new THREE.Vector2();
+ threeRenderer.getSize(v2);
+ const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
+ object.position.z = 3;
+
+ createObject(args.CAMERA, object, args.GROUP);
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA);
+ object[args.PROPERTY] = args.VALUE;
+ object.updateProjectionMatrix();
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA);
+ const value = JSON.stringify(object[args.PROPERTY]);
+ return value;
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA);
+ if (!object) return;
+ camera = object;
+ //reset composer, else it does not update.
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
+
+ cubeCamera(args) {
+ // Create cube render target
+ const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
+ generateMipmaps: true,
+ });
+ // Create cube camera
+ const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
+ createObject(args.CAMERA, cubeCamera, args.GROUP);
+
+ renderTargets[args.RT] = {
+ target: cubeRenderTarget,
+ camera: cubeCamera,
+ };
+ assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
+ }
+
+ renderTarget(args) {
+ let object = getObject(args.CAMERA);
+ const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
+ generateMipmaps: false,
+ });
+
+ renderTargets[args.RT] = {
+ target: renderTarget,
+ camera: object,
+ };
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H);
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture;
+ console.log(t, renderTargets[args.RT]);
+ return `renderTargets/${t.uuid}`;
+ }
+ removeTarget(args) {
+ delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
+ renderTargets[args.RT].target.dispose();
+ delete renderTargets[args.RT];
+ }
+ }
+ Scratch.extensions.register(new ThreeCameras());
+
+ class ThreeObjects {
+ getInfo() {
+ return {
+ id: "threeObjects",
+ name: "Three Objects",
+ color1: "#38c567ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add object [OBJECT3D] [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "cloneObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myClone",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "setObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "getObject",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of object [OBJECT3D]",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectProperties",
+ },
+ },
+ },
+ {
+ opcode: "objectE",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there an object [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "removeObject",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove object [OBJECT3D] from scene",
+ arguments: {
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: " ↳ Transforms",
+ },
+ {
+ opcode: "setObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
+ //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
+ {
+ opcode: "getObjectV3",
+ extensions: ["colours_motion"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get [PROPERTY] of [OBJECT3D]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectVector3",
+ defaultValue: "position",
+ },
+ OBJECT3D: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Materials",
+ },
+ {
+ opcode: "newMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new material [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialTypes",
+ defaultValue: "MeshStandardMaterial",
+ },
+ },
+ },
+ {
+ opcode: "materialE",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a material [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "removeMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove material [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ },
+ },
+ {
+ opcode: "setMaterial",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [PROPERTY] of [NAME] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "materialProperties",
+ defaultValue: "color",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "new Color()",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "setBlending",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] blending to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ },
+ },
+ },
+ {
+ opcode: "setDepth",
+ extensions: ["colours_looks"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set material [NAME] depth to [VALUE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myMaterial",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "depthModes",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Geometries",
+ },
+ {
+ opcode: "newGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new geometry [NAME] [TYPE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "geometryTypes",
+ defaultValue: "BoxGeometry",
+ },
+ },
+ },
+ {
+ opcode: "geometryE",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is there a geometry [NAME]?",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ {
+ opcode: "removeGeometry",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "newGeo",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "new empty geometry [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoPoints",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] vertex points to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[points]",
+ },
+ },
+ },
+ {
+ opcode: "geoUVs",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set geometry [NAME] UVs to [POINTS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myGeometry",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[UVs]",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "splines",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create spline [NAME] from curve [CURVE]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "splineModel",
+ extensions: ["colours_operators"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySpline",
+ },
+ MODEL: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ CURVE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[curve]",
+ exemptFromNormalization: true,
+ },
+ SPACING: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Convert font to JSON",
+ func: "openConv",
+ },
+ {
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load JSON font file",
+ func: "loadFont",
+ },
+ {
+ opcode: "text",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myText",
+ },
+ TEXT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "C-369",
+ },
+ FONT: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "fonts",
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ D: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ CS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 6,
+ },
+ },
+ },
+ ],
+ menus: {
+ objectVector3: {
+ acceptReporters: false,
+ items: [{
+ text: "Positon",
+ value: "position",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Facing Direction (.up)",
+ value: "up",
+ },
+ ],
+ },
+ objectProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Geometry",
+ value: "geometry",
+ },
+ {
+ text: "Material",
+ value: "material",
+ },
+ {
+ text: "Visible (true/false)",
+ value: "visible",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh",
+ value: "Mesh",
+ },
+ {
+ text: "Sprite",
+ value: "Sprite",
+ },
+ {
+ text: "Points",
+ value: "Points",
+ },
+ {
+ text: "Line",
+ value: "Line",
+ },
+ {
+ text: "Group",
+ value: "Group",
+ },
+ ],
+ },
+ XYZ: {
+ acceptReporters: false,
+ items: [{
+ text: "X",
+ value: "x",
+ },
+ {
+ text: "Y",
+ value: "y",
+ },
+ {
+ text: "Z",
+ value: "z",
+ },
+ ],
+ },
+ materialProperties: {
+ acceptReporters: false,
+ items: [
+ "|GENERAL| <-- not a property",
+ {
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map",
+ value: "map",
+ },
+ {
+ text: "Opacity",
+ value: "opacity",
+ },
+ {
+ text: "Transparent",
+ value: "transparent",
+ },
+ {
+ text: "Alpha Map",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test",
+ value: "alphaTest",
+ },
+ {
+ text: "Depth Test",
+ value: "depthTest",
+ },
+ {
+ text: "Depth Write",
+ value: "depthWrite",
+ },
+ {
+ text: "Color Write",
+ value: "colorWrite",
+ },
+ {
+ text: "Side",
+ value: "side",
+ },
+ {
+ text: "Visible",
+ value: "visible",
+ },
+ /*
+ { text: "Blending", value: "blending" },
+ { text: "Blend Src", value: "blendSrc" },
+ { text: "Blend Dst", value: "blendDst" },
+ { text: "Blend Equation", value: "blendEquation" },
+ { text: "Blend Src Alpha", value: "blendSrcAlpha" },
+ { text: "Blend Dst Alpha", value: "blendDstAlpha" },
+ { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
+ {
+ text: "Blend Aplha",
+ value: "blendAplha",
+ },
+ {
+ text: "Blend Color",
+ value: "blendColor",
+ },
+ {
+ text: "Alpha Hash",
+ value: "alphaHash",
+ },
+ {
+ text: "Premultiplied Alpha",
+ value: "premultipliedAlpha",
+ },
+
+ {
+ text: "Tone Mapped",
+ value: "toneMapped",
+ },
+ {
+ text: "Fog",
+ value: "fog",
+ },
+ {
+ text: "Flat Shading",
+ value: "flatShading",
+ },
+
+ "|MESH Standard / Physical| <-- not a property",
+ {
+ text: "Metalness",
+ value: "metalness",
+ },
+ {
+ text: "Metalness Map",
+ value: "metalnessMap",
+ },
+ {
+ text: "Roughness",
+ value: "roughness",
+ },
+ {
+ text: "Reflectivity",
+ value: "reflectivity",
+ },
+ {
+ text: "Roughness Map",
+ value: "roughnessMap",
+ },
+ {
+ text: "Emissive",
+ value: "emissive",
+ },
+ {
+ text: "Emissive Intensity",
+ value: "emissiveIntensity",
+ },
+ {
+ text: "Emissive Map",
+ value: "emissiveMap",
+ },
+ {
+ text: "Env Map",
+ value: "envMap",
+ },
+ {
+ text: "Env Map Intensity",
+ value: "envMapIntensity",
+ },
+ {
+ text: "Env Map Rotation",
+ value: "envMapRotation",
+ },
+ {
+ text: "Ior",
+ value: "ior",
+ },
+ {
+ text: "Refraction Ratio",
+ value: "refractionRatio",
+ },
+ {
+ text: "Clearcoat",
+ value: "clearcoat",
+ },
+ {
+ text: "Clearcoat Map",
+ value: "clearcoatMap",
+ },
+ {
+ text: "Clearcoat Roughness",
+ value: "clearcoatRoughness",
+ },
+ {
+ text: "Clearcoat Roughness Map",
+ value: "clearcoatRoughnessMap",
+ },
+ {
+ text: "Dispersion",
+ value: "dispersion",
+ },
+ {
+ text: "Sheen",
+ value: "sheen",
+ },
+ {
+ text: "Sheen Color",
+ value: "sheenColor",
+ },
+ {
+ text: "Sheen Color Map",
+ value: "sheenColorMap",
+ },
+ {
+ text: "Sheen Roughness",
+ value: "sheenRoughness",
+ },
+ {
+ text: "Sheen Roughness Map",
+ value: "sheenRoughnessMap",
+ },
+ {
+ text: "Specular Color",
+ value: "specularColor",
+ },
+ {
+ text: "Specular Color Map",
+ value: "specularColorMap",
+ },
+ {
+ text: "Specular Intensity",
+ value: "specularIntensity",
+ },
+ {
+ text: "Specular Intensity Map",
+ value: "specularIntensityMap",
+ },
+ {
+ text: "Transmission",
+ value: "transmission",
+ },
+ {
+ text: "Transmission Map",
+ value: "transmissionMap",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Thickness Map",
+ value: "thicknessMap",
+ },
+ {
+ text: "Anisotropy",
+ value: "anisotropy",
+ },
+ {
+ text: "Anisotropy Map",
+ value: "anisotropyMap",
+ },
+ {
+ text: "Anisotropy Rotation",
+ value: "anisotropyRotation",
+ },
+ {
+ text: "Attenuation Distance",
+ value: "attenuationDistance",
+ },
+ {
+ text: "Attenuation Color",
+ value: "attenuationColor",
+ },
+ {
+ text: "Thickness",
+ value: "thickness",
+ },
+ {
+ text: "Iridescence",
+ value: "iridescence",
+ },
+ {
+ text: "Iridescence Ior",
+ value: "iridescenceIOR",
+ },
+ {
+ text: "Iridescence Map",
+ value: "iridescenceMap",
+ },
+ {
+ text: "Iridescence Thickness Range",
+ value: "iridescenceThicknessRange",
+ },
+
+ "|MESH Displacement / Normal / Bump| <-- not a property",
+ {
+ text: "Displacement Map",
+ value: "displacementMap",
+ },
+ {
+ text: "Displacement Scale",
+ value: "displacementScale",
+ },
+ {
+ text: "Displacement Bias",
+ value: "displacementBias",
+ },
+ {
+ text: "Bump Map",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ {
+ text: "Normal Map Type",
+ value: "normalMapType",
+ },
+
+ "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
+ {
+ text: "Shininess",
+ value: "shininess",
+ },
+
+ {
+ text: "Wireframe",
+ value: "wireframe",
+ },
+ {
+ text: "Wireframe Linewidth",
+ value: "wireframeLinewidth",
+ },
+ {
+ text: "Wireframe Linecap",
+ value: "wireframeLinecap",
+ },
+ {
+ text: "Wireframe Linejoin",
+ value: "wireframeLinejoin",
+ },
+
+ "|POINTS| <-- not a property",
+ {
+ text: "Size",
+ value: "size",
+ },
+ {
+ text: "Size Attenuation",
+ value: "sizeAttenuation",
+ },
+
+ "|LINES| <-- not a property",
+ {
+ text: "Scale",
+ value: "scale",
+ },
+ {
+ text: "Dash Size",
+ value: "dashSize",
+ },
+ {
+ text: "Gap Size",
+ value: "gapSize",
+ },
+
+ "|SPRITES| <-- not a property",
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [{
+ text: "No Blending",
+ value: "NoBlending",
+ },
+ {
+ text: "Normal Blending",
+ value: "NormalBlending",
+ },
+ {
+ text: "Additive Blending",
+ value: "AdditiveBlending",
+ },
+ {
+ text: "Subtractive Blending",
+ value: "SubtractiveBlending",
+ },
+ {
+ text: "Multiply Blending",
+ value: "MultiplyBlending",
+ },
+ {
+ text: "Custom Blending",
+ value: "CustomBlending",
+ },
+ ],
+ },
+ depthModes: {
+ acceptReporters: false,
+ items: [{
+ text: "Never Depth",
+ value: "NeverDepth",
+ },
+ {
+ text: "Always Depth",
+ value: "AlwaysDepth",
+ },
+ {
+ text: "Equal Depth",
+ value: "EqualDepth",
+ },
+ {
+ text: "Less Depth",
+ value: "LessDepth",
+ },
+ {
+ text: "Less Equal Depth",
+ value: "LessEqualDepth",
+ },
+ {
+ text: "Greater Equal Depth",
+ value: "GreaterEqualDepth",
+ },
+ {
+ text: "Greater Depth",
+ value: "GreaterDepth",
+ },
+ {
+ text: "Not Equal Depth",
+ value: "NotEqualDepth",
+ },
+ ],
+ },
+ materialTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Mesh Basic Material",
+ value: "MeshBasicMaterial",
+ },
+ {
+ text: "Mesh Standard Material",
+ value: "MeshStandardMaterial",
+ },
+ {
+ text: "Mesh Physical Material",
+ value: "MeshPhysicalMaterial",
+ },
+ {
+ text: "Mesh Lambert Material",
+ value: "MeshLambertMaterial",
+ },
+ {
+ text: "Mesh Phong Material",
+ value: "MeshPhongMaterial",
+ },
+ {
+ text: "Mesh Depth Material",
+ value: "MeshDepthMaterial",
+ },
+ {
+ text: "Mesh Normal Material",
+ value: "MeshNormalMaterial",
+ },
+ {
+ text: "Mesh Matcap Material",
+ value: "MeshMatcapMaterial",
+ },
+ {
+ text: "Mesh Toon Material",
+ value: "MeshToonMaterial",
+ },
+ {
+ text: "Line Basic Material",
+ value: "LineBasicMaterial",
+ },
+ {
+ text: "Line Dashed Material",
+ value: "LineDashedMaterial",
+ },
+ {
+ text: "Points Material",
+ value: "PointsMaterial",
+ },
+ {
+ text: "Sprite Material",
+ value: "SpriteMaterial",
+ },
+ {
+ text: "Shadow Material",
+ value: "ShadowMaterial",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ geometryTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box Geometry",
+ value: "BoxGeometry",
+ },
+ {
+ text: "Sphere Geometry",
+ value: "SphereGeometry",
+ },
+ {
+ text: "Cylinder Geometry",
+ value: "CylinderGeometry",
+ },
+ {
+ text: "Plane Geometry",
+ value: "PlaneGeometry",
+ },
+ {
+ text: "Circle Geometry",
+ value: "CircleGeometry",
+ },
+ {
+ text: "Torus Geometry",
+ value: "TorusGeometry",
+ },
+ {
+ text: "Torus Knot Geometry",
+ value: "TorusKnotGeometry",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model! (GLB Loader category)"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ fonts: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".json"));
+ if (models.length < 1) return [
+ ["Load a font!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ addObject(args) {
+ const object = new THREE[args.TYPE]();
+
+ object.castShadow = true;
+ object.receiveShadow = true;
+
+ createObject(args.OBJECT3D, object, args.GROUP);
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D);
+ const clone = object.clone(true);
+ clone.name;
+ createObject(args.NAME, clone, args.GROUP);
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ let values = JSON.parse(args.VALUE);
+
+ function degToRad(deg) {
+ return (deg * Math.PI) / 180;
+ }
+
+ if (object.rigidBody) {
+ const x = values[0];
+ const y = values[1];
+ const z = values[2];
+ if (args.PROPERTY === "rotation") {
+ const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
+ const quaternion = new THREE.Quaternion();
+ quaternion.setFromEuler(euler);
+
+ object.rigidBody.setRotation({
+ x: quaternion.x,
+ y: quaternion.y,
+ z: quaternion.z,
+ w: quaternion.w,
+ });
+ } else if (args.PROPERTY === "position") {
+ object.rigidBody.setTranslation({
+ x: x,
+ y: y,
+ z: z,
+ },
+ true
+ );
+ }
+ return;
+ }
+
+ if (object.isCamera == true && controls) {}
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map((v) => (v * Math.PI) / 180);
+ object.rotation.set(0, 0, 0);
+ }
+ if (object.isDirectionalLight == true) {
+ object.pos = new THREE.Vector3(...values);
+ console.log(true, values, object.pos);
+ return;
+ }
+ object[args.PROPERTY].set(...values);
+
+ if (object.type == "CubeCamera") object.updateCoordinateSystem();
+ }
+ /*
+ changeObjectV3(args) {
+ getObject(args.OBJECT3D)
+ let values = JSON.parse(args.VALUE)
+
+ if (args.PROPERTY === "rotation") {
+ values = values.map(v => v * Math.PI / 180);
+ object.rotation.x += values[0]
+ object.rotation.y += values[1]
+ object.rotation.z += values[2]
+ }
+ else {
+ object[args.PROPERTY].add(...values);
+ }
+ }
+ changeObjectXV3(args) {
+ getObject(args.OBJECT3D)
+ let value = args.VALUE
+ if (args.PROPERTY === "rotation") value = value * Math.PI / 180
+
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let values = vector3ToString(object[args.PROPERTY]);
+ if (args.PROPERTY === "rotation") {
+ const toDeg = Math.PI / 180;
+ values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
+ }
+
+ return JSON.stringify(values);
+ }
+ setObject(args) {
+ let object = getObject(args.OBJECT3D);
+ let value = args.VALUE;
+ if (args.PROPERTY === "material") {
+ const mat = materials[args.NAME];
+ if (mat) value = mat;
+ else value = undefined;
+ } else if (args.PROPERTY === "geometry") {
+ const geo = geometries[args.NAME];
+ if (geo) value = geo;
+ else value = undefined;
+ } else value = !!value;
+
+ if (value == undefined) return; //invalid geo/mat
+ object[args.PROPERTY] = value;
+ }
+ getObject(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let value;
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value;
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D);
+ }
+ objectE(args) {
+ return scene.children.map((o) => o.name).includes(args.NAME);
+ }
+
+ //defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
+
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
+ const mat = materials[args.NAME];
+
+ let value = args.VALUE;
+
+ if (args.VALUE == "false") value = false;
+
+ if (args.PROPERTY == "side") {
+ value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
+ } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
+ else value = getAsset(value);
+
+ console.log("o:", args.VALUE, typeof args.VALUE);
+ console.log("r:", value, typeof value);
+
+ mat[args.PROPERTY] = await value; //await incase its a texture
+ mat.needsUpdate = true;
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME];
+ mat.blending = THREE[args.VALUE];
+ mat.premultipliedAlpha = true;
+ mat.needsUpdate = true;
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME];
+ mat.depthFunc = THREE[args.VALUE];
+ mat.needsUpdate = true;
+ }
+ removeMaterial(args) {
+ const mat = materials[args.NAME];
+ mat.dispose();
+ delete materials[args.NAME];
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false;
+ }
+
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ const geo = new THREE[args.TYPE]();
+ geo.name = args.NAME;
+
+ geometries[args.NAME] = geo;
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo[args.PROPERTY] = args.VALUE;
+
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo.dispose();
+ delete geometries[args.NAME];
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false;
+ }
+
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry();
+ geometry.name = args.NAME;
+ geometries[args.NAME] = geometry;
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME];
+ const positions = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v3 of each vertex of each triangle
+
+ geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
+ geometry.computeVertexNormals();
+
+ geometry.needsUpdate = true;
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME];
+ const UVs = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v2 of each UV of each triangle
+
+ geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
+ geometry.needsUpdate = true;
+ }
+
+ splines(args) {
+ const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
+ geometry.name = args.NAME;
+
+ geometries[args.NAME] = geometry;
+ }
+
+ async splineModel(args) {
+ const model = await getModel(args.MODEL, args.NAME);
+ if (!model) return console.warn("Model not found:", args.MODEL);
+
+ const curve = getAsset(args.CURVE);
+ const spacing = parseFloat(args.SPACING) || 1;
+ const curveLength = curve.getLength();
+ const divisions = Math.floor(curveLength / spacing);
+
+ const geomList = [];
+ const matList = [];
+
+ for (let i = 0; i <= divisions; i++) {
+ const t = i / divisions;
+ const pos = curve.getPointAt(t);
+ const tangent = curve.getTangentAt(t);
+
+ const temp = model.clone(true);
+ temp.position.copy(pos);
+
+ const up = new THREE.Vector3(0, 0, 1);
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
+ temp.quaternion.copy(quat);
+
+ temp.updateMatrixWorld(true);
+
+ temp.traverse((child) => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone();
+ geom.applyMatrix4(child.matrixWorld);
+ geomList.push(geom);
+ matList.push(child.material); //.clone() ?
+ }
+ });
+ }
+
+ const validGeoms = geomList.filter((g) => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
+ if (!ok) console.warn("geometry skipped:", g);
+ return ok;
+ });
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
+ merged.computeBoundingBox();
+ merged.computeBoundingSphere();
+
+ merged.name = args.NAME;
+ geometries[args.NAME] = merged;
+ matList.name = args.NAME;
+ materials[args.NAME] = matList;
+ }
+
+ async text(args) {
+ const fontFile = runtime
+ .getTargetForStage()
+ .getSounds()
+ .find((c) => c.name === args.FONT);
+ if (!fontFile) return;
+
+ const json = new TextDecoder().decode(fontFile.asset.data.buffer);
+ const fontData = JSON.parse(json);
+
+ const font = fontLoad.parse(fontData);
+
+ const params = {
+ font: font,
+ size: JSON.parse(args.S),
+ height: JSON.parse(args.D),
+ curveSegments: JSON.parse(args.CS),
+ bevelEnabled: false,
+ };
+ const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
+ geometry.computeVertexNormals();
+ geometry.center(); // optional, recenters the text
+
+ geometry.name = args.NAME;
+
+ geometries[args.NAME] = geometry;
+ }
+
+ async loadFont() {
+ openFileExplorer(".json").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ // From lily's assets
+ // // Thank you PenguinMod for providing this code.
+
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Font loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading font.");
+ }
+
+ // End of PenguinMod
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ openConv() {
+ {
+ open("https://gero3.github.io/facetype.js/");
+ }
+ }
+ }
+ Scratch.extensions.register(new ThreeObjects());
+
+ class ThreeLights {
+ getInfo() {
+ return {
+ id: "threeLights",
+ name: "Three Lights",
+ color1: "#c7a22aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "addLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add light [NAME] type [TYPE] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightTypes",
+ },
+ },
+ },
+ {
+ opcode: "setLight",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set light [NAME][PROPERTY] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lightProperties",
+ defaultValue: "intensity",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myLight",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ lightTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Ambient Light",
+ value: "AmbientLight",
+ },
+ {
+ text: "Directional Light",
+ value: "DirectionalLight",
+ },
+ {
+ text: "Point Light",
+ value: "PointLight",
+ },
+ {
+ text: "Hemisphere Light",
+ value: "HemisphereLight",
+ },
+ {
+ text: "Spot Light",
+ value: "SpotLight",
+ },
+ ],
+ },
+ lightProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Intensity",
+ value: "intensity",
+ },
+ {
+ text: "Cast Shadow?",
+ value: "castShadow",
+ },
+ {
+ text: "Ground Color (HemisphereLight)",
+ value: "groundColor",
+ },
+ {
+ text: "Map (SpotLight)",
+ value: "map",
+ },
+ {
+ text: "Distance (SpotLight)",
+ value: "distance",
+ },
+ {
+ text: "Decay (SpotLight)",
+ value: "decay",
+ },
+ {
+ text: "Penumbra (SpotLight)",
+ value: "penumbra",
+ },
+ {
+ text: "Angle/Size (SpotLight)",
+ value: "angle",
+ },
+ {
+ text: "Power (SpotLight)",
+ value: "power",
+ },
+ {
+ text: "Target Position (Directional/SpotLight)",
+ value: "target",
+ },
+ ],
+ },
+ },
+ };
+ }
+
+ addLight(args) {
+ const light = new THREE[args.TYPE](0xffffff, 1);
+
+ createObject(args.NAME, light, args.GROUP);
+ lights[args.NAME] = light;
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
+
+ light.castShadow = true;
+ if (light.type === "PointLight") return;
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0);
+ scene.add(light.target);
+
+ light.pos = new THREE.Vector3(0, 0, 0);
+
+ light.shadow.mapSize.width = 4096;
+ light.shadow.mapSize.height = 2048;
+
+ if (light.type === "SpotLight") {
+ light.decay = 0;
+ light.shadow.camera.near = 500;
+ light.shadow.camera.far = 4000;
+ light.shadow.camera.fov = 30;
+ }
+ light.shadow.needsUpdate = true;
+ light.needsUpdate = true;
+ }
+
+ setLight(args) {
+ const light = lights[args.NAME];
+ if (!args.PROPERTY) return;
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)); //vector3
+ light.target.updateMatrixWorld();
+ } else {
+ light[args.PROPERTY] = getAsset(args.VALUE);
+ }
+ light.needsUpdate = true;
+
+ if (light.type === "AmbientLight" || "HemisphereLight") return;
+
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
+ }
+ Scratch.extensions.register(new ThreeLights());
+
+ class ThreeUtilities {
+ getInfo() {
+ return {
+ id: "threeUtility",
+ name: "Three Utilities",
+ color1: "#3875c5ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "newVector2",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ {
+ opcode: "newVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Vector [X] [Y] [Z]",
+ arguments: {
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ Z: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "operateV3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "do [V3] [O] [V32]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ O: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "operators",
+ },
+ V32: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "moveVector3",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "move [S] steps in vector [V3] in direction [D3]",
+ arguments: {
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "directionTo",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "direction from [V3] to [T3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ T3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "newColor",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Color [HEX]",
+ arguments: {
+ HEX: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ },
+ },
+ },
+ {
+ opcode: "newFog",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Fog [COLOR] [NEAR] [FAR]",
+ arguments: {
+ COLOR: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#9966ff",
+ exemptFromNormalization: true,
+ },
+ NEAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ },
+ FAR: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 10,
+ },
+ },
+ },
+ {
+ opcode: "newTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newCubeTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
+ arguments: {
+ COSTUMEX0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEX1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEY1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ0: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ COSTUMEZ1: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ STYLE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureStyles",
+ },
+ X: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ Y: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ {
+ opcode: "newEquirectangularTexture",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "New Equirectangular Texture [COSTUME] [MODE]",
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "textureModes",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "curve",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
+ arguments: {
+ TYPE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "curveTypes",
+ },
+ POINTS: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
+ },
+ CLOSED: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "mouseDown",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "mouse [BUTTON] [action]?",
+ arguments: {
+ BUTTON: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseButtons",
+ },
+ action: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "mouseAction",
+ },
+ },
+ },
+ {
+ opcode: "mousePos",
+ extensions: ["colours_sensing"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "mouse position",
+ arguments: {},
+ },
+ "---",
+ {
+ opcode: "getItem",
+ extensions: ["colours_data_lists"],
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get item [ITEM] of [ARRAY]",
+ arguments: {
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ ARRAY: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: `["myObject", "myLight"]`,
+ },
+ },
+ },
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "↳ Raycasting",
+ },
+ {
+ opcode: "raycast",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "Raycast from [V3] in direction [D3]",
+ arguments: {
+ V3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,3]",
+ },
+ D3: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,1]",
+ },
+ },
+ },
+ {
+ opcode: "getRaycast",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get raycast [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "raycastProperties",
+ },
+ },
+ },
+ ],
+ menus: {
+ materialProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Color",
+ value: "color",
+ },
+ {
+ text: "Map (texture)",
+ value: "map",
+ },
+ {
+ text: "Alpha Map (texture)",
+ value: "alphaMap",
+ },
+ {
+ text: "Alpha Test (0-1)",
+ value: "alphaTest",
+ },
+ {
+ text: "Side (front/back/double)",
+ value: "side",
+ },
+ {
+ text: "Bump Map (texture)",
+ value: "bumpMap",
+ },
+ {
+ text: "Bump Scale",
+ value: "bumpScale",
+ },
+ ],
+ },
+ textureModes: {
+ acceptReporters: false,
+ items: ["Pixelate", "Blur"],
+ },
+ textureStyles: {
+ acceptReporters: false,
+ items: ["Repeat", "Clamp"],
+ },
+ raycastProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Intersected Object Names",
+ value: "name",
+ },
+ {
+ text: "Number of Objects",
+ value: "number",
+ },
+ {
+ text: "Intersected Objects distances",
+ value: "distance",
+ },
+ ],
+ },
+ mouseButtons: {
+ acceptReporters: false,
+ items: ["left", "middle", "right"],
+ },
+ mouseAction: {
+ acceptReporters: false,
+ items: ["Down", "Clicked"],
+ },
+ curveTypes: {
+ acceptReporters: false,
+ items: ["CatmullRomCurve3"],
+ },
+ operators: {
+ acceptReporters: false,
+ items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
+ },
+ },
+ };
+ }
+ mouseDown(args) {
+ if (args.action === "Down") return isMouseDown[args.BUTTON];
+ if (args.action === "Clicked") {
+ if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
+ else prevMouse[args.BUTTON] = true;
+ return true;
+ }
+ }
+ mousePos(event) {
+ return JSON.stringify(mouseNDC);
+ }
+ newVector3(args) {
+ return JSON.stringify([args.X, args.Y, args.Z]);
+ }
+ operateV3(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const v32 = new THREE.Vector3(...JSON.parse(args.V32));
+
+ let r;
+ if (args.O == "+") r = v3.add(v32);
+ else if (args.O == "-") r = v3.sub(v32);
+ else if (args.O == "*") r = v3.multiply(v32);
+ else if (args.O == "/") r = v3.divide(v32);
+ else if (args.O == "=") r = v3.equals(v32);
+ else if (args.O == "max") r = v3.max(v32);
+ else if (args.O == "min") r = v3.min(v32);
+ else if (args.O == "dot") r = v3.dot(v32);
+ else if (args.O == "cross") r = v3.cross(v32);
+ else if (args.O == "distance to") r = v3.distanceTo(v32);
+ else if (args.O == "angle to") r = v3.angleTo(v32);
+ else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
+
+ if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
+ else return JSON.stringify(r);
+ }
+
+ newVector2(args) {
+ return JSON.stringify([args.X, args.Y]);
+ }
+
+ moveVector3(args) {
+ const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
+ const steps = Number(args.S);
+
+ const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
+
+ const yaw = THREE.MathUtils.degToRad(yawInputDeg);
+ const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
+ const roll = THREE.MathUtils.degToRad(rollInputDeg);
+
+ const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
+
+ const forwardVector = new THREE.Vector3(0, 0, -1);
+ const direction = forwardVector.applyEuler(euler).normalize();
+
+ const newPos = currentPos.add(direction.multiplyScalar(steps));
+ return JSON.stringify([newPos.x, newPos.y, newPos.z]);
+ }
+
+ directionTo(args) {
+ const v3 = new THREE.Vector3(...JSON.parse(args.V3));
+ const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
+
+ const direction = toV3.clone().sub(v3).normalize();
+ // Pitch (X)
+ const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
+ // Yaw (Y)
+ const yaw = Math.atan2(direction.x, direction.z);
+
+ // Roll always 0
+ return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
+ }
+
+ newColor(args) {
+ const color = new THREE.Color(args.HEX);
+ const uuid = crypto.randomUUID();
+ assets.colors[uuid] = color;
+ return `colors/${uuid}`;
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
+ const uuid = crypto.randomUUID();
+ assets.fogs[uuid] = fog;
+ return `fogs/${uuid}`;
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newCubeTexture(args) {
+ const uris = [
+ encodeCostume(args.COSTUMEX0),
+ encodeCostume(args.COSTUMEX1),
+ encodeCostume(args.COSTUMEY0),
+ encodeCostume(args.COSTUMEY1),
+ encodeCostume(args.COSTUMEZ0),
+ encodeCostume(args.COSTUMEZ1),
+ ];
+ const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
+
+ texture.name = "CubeTexture" + args.COSTUMEX0;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+ texture.mapping = THREE.EquirectangularReflectionMapping;
+
+ setTexutre(texture, args.MODE);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+
+ curve(args) {
+ function parsePoints(input) {
+ // Match all [x,y,z] groups
+ const matches = input.match(/\[([^\]]+)\]/g);
+ if (!matches) return [];
+
+ return matches.map((str) => {
+ const nums = str
+ .replace(/[\[\]\s]/g, "")
+ .split(",")
+ .map(Number);
+ return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
+ });
+ }
+ const points = parsePoints(args.POINTS);
+ const curve = new THREE[args.TYPE](points);
+ curve.closed = JSON.parse(args.CLOSED);
+
+ const uuid = crypto.randomUUID();
+ assets.curves[uuid] = curve;
+ return `curves/${uuid}`;
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY);
+ const item = items[args.ITEM - 1];
+ if (!item) return "0";
+ return item;
+ }
+
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3));
+ // rotation is in degrees => convert to radians first
+ const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
+
+ const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
+ const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
+
+ const raycaster = new THREE.Raycaster();
+ //const camera = getObject(args.CAMERA)
+ raycaster.set(origin, direction);
+
+ const intersects = raycaster.intersectObjects(scene.children, true);
+
+ raycastResult = intersects;
+ }
+ getRaycast(args) {
+ if (args.PROPERTY === "number") return raycastResult.length;
+ if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
+ return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
+ }
+ }
+ Scratch.extensions.register(new ThreeUtilities());
+
+ class ThreeGLB {
+ getInfo() {
+ return {
+ id: "threeGLB",
+ name: "Three GLB Loader",
+ color1: "#c53838ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Load GLB File",
+ func: "loadModelFile",
+ },
+ {
+ opcode: "addModel",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add [ITEM] as [NAME] to [GROUP]",
+ arguments: {
+ GROUP: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "scene",
+ },
+ ITEM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelsList",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ },
+ },
+ {
+ opcode: "getModel",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get object [PROPERTY] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "modelProperties",
+ },
+ },
+ },
+ {
+ opcode: "playAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "play animation [ANAME] of [NAME], [TIMES] times",
+ arguments: {
+ TIMES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "pauseAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [TOGGLE] animation [ANAME] of [NAME]",
+ arguments: {
+ TOGGLE: {
+ type: Scratch.ArgumentType.NUMBER,
+ menu: "pauseUn",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ {
+ opcode: "stopAnimation",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stop animation [ANAME] of [NAME]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myModel",
+ },
+ ANAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "walk",
+ exemptFromNormalization: true,
+ },
+ },
+ },
+ ],
+ menus: {
+ modelProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Animations",
+ value: "animations",
+ }, ],
+ },
+ pauseUn: {
+ acceptReporters: true,
+ items: [{
+ text: "Pause",
+ value: "true",
+ },
+ {
+ text: "Unpasue",
+ value: "false",
+ },
+ ],
+ },
+ modelsList: {
+ acceptReporters: false,
+ items: () => {
+ const stage = runtime.getTargetForStage();
+ if (!stage) return ["(loading...)"];
+
+ // @ts-ignore
+ const models = Scratch.vm.runtime
+ .getTargetForStage()
+ .getSounds()
+ .filter((e) => e.name && e.name.endsWith(".glb"));
+ if (models.length < 1) return [
+ ["Load a model!"]
+ ];
+
+ // @ts-ignore
+ return models.map((m) => [m.name]);
+ },
+ },
+ },
+ };
+ }
+
+ async loadModelFile() {
+ openFileExplorer(".glb").then((files) => {
+ const file = files[0];
+ const reader = new FileReader();
+
+ reader.onload = async (e) => {
+ const arrayBuffer = e.target.result;
+
+ {
+ // From lily's assets
+
+ // Thank you PenguinMod for providing this code.
+ {
+ const targetId = runtime.getTargetForStage().id; //util.target.id not working!
+ const assetName = Cast.toString(file.name);
+
+ //const res = await Scratch.fetch(args.URL);
+ //const buffer = await res.arrayBuffer();
+ const buffer = arrayBuffer;
+
+ const storage = runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.MP3,
+ // @ts-ignore
+ new Uint8Array(buffer),
+ null,
+ true
+ );
+
+ try {
+ await vm.addSound(
+ // @ts-ignore
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ alert("Model loaded successfully!");
+ } catch (e) {
+ console.error(e);
+ alert("Error loading model.");
+ }
+ }
+ // End of PenguinMod
+ }
+ };
+
+ reader.readAsArrayBuffer(file);
+ });
+ }
+ async addModel(args) {
+ const group = await getModel(args.ITEM, args.NAME);
+
+ createObject(args.NAME, group, args.GROUP);
+ }
+ getModel(args) {
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString();
+ }
+
+ playAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) {
+ console.log("no model!");
+ return;
+ }
+
+ const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
+ if (!action) {
+ console.log("no action!");
+ return;
+ }
+
+ args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
+
+ action.reset().play();
+ }
+ stopAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.stop();
+ }
+ pauseAnimation(args) {
+ const model = models[args.NAME];
+ if (!model) return;
+
+ const action = model.actions[args.ANAME];
+ if (action) action.paused = args.TOGGLE;
+ }
+ }
+ Scratch.extensions.register(new ThreeGLB());
+
+ class ThreeAddons {
+ getInfo() {
+ return {
+ id: "threeAddons",
+ name: "Three Addons",
+ color1: "#c538a2ff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ blockType: Scratch.BlockType.LABEL,
+ text: "Orbit Control",
+ },
+ {
+ opcode: "OrbitControl",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set addon Orbit Control [STATE]",
+ arguments: {
+ STATE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "onoff",
+ },
+ },
+ },
+
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "Post Processing",
+ },
+ {
+ opcode: "resetComposer",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset composer",
+ },
+ {
+ opcode: "bloom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ I: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ T: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.5,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "godRays",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ DEC: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.95,
+ },
+ DENS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ EXP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.1,
+ },
+ WEI: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.4,
+ },
+ RES: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ SAMP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 64,
+ },
+ },
+ },
+ {
+ opcode: "dots",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ S: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ A: {
+ type: Scratch.ArgumentType.ANGLE,
+ defaultValue: 0,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "SCREEN",
+ },
+ },
+ },
+ {
+ opcode: "depth",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ FD: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 3,
+ },
+ FL: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0.001,
+ },
+ BS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 4,
+ },
+ H: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 240,
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "custom",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
+ arguments: {
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myShader",
+ },
+ FRA: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ VER: {
+ type: Scratch.ArgumentType.STRING,
+ },
+ BLEND: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "blendModes",
+ defaultValue: "NORMAL",
+ },
+ OP: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1,
+ },
+ },
+ },
+ ],
+ menus: {
+ onoff: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "1",
+ },
+ {
+ text: "disabled",
+ value: "0",
+ },
+ ],
+ },
+ blendModes: {
+ acceptReporters: false,
+ items: [
+ "SKIP",
+ "SET",
+ "ADD",
+ "ALPHA",
+ "AVERAGE",
+ "COLOR",
+ "COLOR_BURN",
+ "COLOR_DODGE",
+ "DARKEN",
+ "DIFFERENCE",
+ "DIVIDE",
+ "DST",
+ "EXCLUSION",
+ "HARD_LIGHT",
+ "HARD_MIX",
+ "HUE",
+ "INVERT",
+ "INVERT_RGB",
+ "LIGHTEN",
+ "LINEAR_BURN",
+ "LINEAR_DODGE",
+ "LINEAR_LIGHT",
+ "LUMINOSITY",
+ "MULTIPLY",
+ "NEGATION",
+ "NORMAL",
+ "OVERLAY",
+ "PIN_LIGHT",
+ "REFLECT",
+ "SCREEN",
+ "SRC",
+ "SATURATION",
+ "SOFT_LIGHT",
+ "SUBTRACT",
+ "VIVID_LIGHT",
+ ],
+ },
+ },
+ };
+ }
+
+ OrbitControl(args) {
+ if (controls) controls.dispose();
+
+ console.log("creating...", OrbitControls);
+ controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
+ controls.enableDamping = true;
+
+ controls.enabled = !!args.STATE;
+ console.log(controls);
+ }
+
+ resetComposer() {
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
+
+ bloom(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ bloomEffect.blendMode.opacity.value = args.OP;
+
+ const pass = new EffectPass(camera, bloomEffect);
+
+ composer.addPass(pass);
+ }
+
+ godRays(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ let object = getObject(args.NAME);
+ const sun = object;
+
+ const godRays = new GodRaysEffect(camera, sun, {
+ resolutionScale: args.RES,
+ density: args.DENS, // ray density
+ decay: args.DEC, // fade out
+ weight: args.WEI, // brightness of rays
+ exposure: args.EXP,
+ samples: args.SAMP,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ godRays.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, godRays);
+ composer.addPass(pass);
+ }
+
+ dots(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dot.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, dot);
+ composer.addPass(pass);
+ }
+
+ depth(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dofEffect.blendMode.opacity.value = args.OP;
+
+ const dofPass = new EffectPass(camera, dofEffect);
+ composer.addPass(dofPass);
+ }
+
+ async custom(args) {
+ function cleanGLSL(glslCode) {
+ //delete multilines comments
+ let cleanedCode = glslCode
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
+ .replace(/ /g, "\n")
+ .replace(/\/\/.*$/gm, " ")
+ .replace(/; /g, ";\n");
+
+ return cleanedCode;
+ }
+
+ let fs = cleanGLSL(`
+ ${args.FRA}
+ `);
+ if (!args.FRA.trim()) {
+ fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
+ }
+ const vs = cleanGLSL(`
+ ${args.VER}
+ `);
+ console.log(fs);
+ console.log(vs);
+
+ const effect = new Effect("Custom", fs, {
+ blendFunction: BlendFunction[args.BLEND],
+ vertexShader: vs,
+ uniforms: new Map([
+ //uniforms usually in shaders... open to more!
+ ["time", new THREE.Uniform(0.0)],
+ [
+ "resolution",
+ new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
+ ],
+ ]),
+ defines: new Map([
+ ["USE_TIME", "1"],
+ ["USE_VERTEX_TRANSFORM", ""],
+ ]),
+ });
+
+ effect.blendMode.opacity.value = args.OP;
+
+ const pass = new EffectPass(camera, effect);
+ composer.addPass(pass);
+
+ customEffects.push(effect);
+ }
+ }
+ Scratch.extensions.register(new ThreeAddons());
+
+ class RapierPhysics {
+ getInfo() {
+ return {
+ id: "rapierPhysics",
+ name: "RAPIER Physics",
+ color1: "#222222",
+ color2: "#203024ff",
+ color3: "#78f07eff",
+ blocks: [{
+ opcode: "createWorld",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create world | gravity:[G]",
+ arguments: {
+ G: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,-9.81,0]",
+ },
+ },
+ },
+ {
+ opcode: "getWorld",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get world [PROPERTY]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "wProp",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "objectPhysics",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
+ arguments: {
+ state2: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state2",
+ },
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ type: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "objectTypes",
+ defaultValue: "dynamic",
+ },
+ collider: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderTypes",
+ defaultValue: "cuboid",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ mass: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ density: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "1",
+ },
+ friction: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "0.5",
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- RigidBody",
+ },
+ {
+ opcode: "setRB",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodySets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getRB",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get rigidbody [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "rigidBodyProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "lockObjectAxis",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
+ arguments: {
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "lockAxes",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Y: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ Z: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "tf",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "addForce",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,10,0]",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "forces",
+ defaultValue: "addForce",
+ },
+ SPACE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "spaces",
+ defaultValue: "world",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "resetForces",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "resetF",
+ defaultValue: "resetForces",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "enableCCD",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "enable Continuous Collision Detection for [OBJECT] [state]",
+ arguments: {
+ state: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "state",
+ defaultValue: "true",
+ },
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "oPropS",
+ defaultValue: "physics",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "fixedJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ RA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ RB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ },
+ },
+ {
+ opcode: "sphericalJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ },
+ },
+ {
+ opcode: "revoluteJoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
+ arguments: {
+ ObjA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ ObjB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObjectB",
+ },
+ VA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,0,0]",
+ },
+ VB: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[0,1,0]",
+ },
+ X: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "[1,0,0]",
+ },
+ },
+ },
+ "---",
+ {
+ blockType: Scratch.BlockType.LABEL,
+ text: "- Collider",
+ },
+ {
+ opcode: "setC",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderSets",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ },
+ },
+ },
+ {
+ opcode: "getC",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get collider [PROPERTY] of [OBJECT]",
+ arguments: {
+ PROPERTY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "colliderProperties",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ "---",
+ {
+ opcode: "sensorSingle",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "is sensor [SENSOR] touching [OBJECT]?",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ OBJECT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "myObject",
+ },
+ },
+ },
+ {
+ opcode: "sensorAll",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "objects touching sensor [SENSOR]",
+ arguments: {
+ SENSOR: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "mySensor",
+ },
+ },
+ },
+ ],
+ menus: {
+ wProp: {
+ acceptReporters: false,
+ items: [{
+ text: "Gravity",
+ value: "gravity",
+ },
+ {
+ text: "log to console",
+ value: "log",
+ },
+ ],
+ },
+ tf: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true",
+ value: "true",
+ },
+ ],
+ },
+ lockAxes: {
+ acceptReporters: false,
+ items: [{
+ text: "Translation",
+ value: "setEnabledTranslations",
+ },
+ {
+ text: "Rotation",
+ value: "setEnabledRotations",
+ },
+ ],
+ },
+ rigidBodyProperties: {
+ acceptReporters: false,
+ items: [{
+ text: "Type",
+ value: "bodyType",
+ },
+ {
+ text: "Linear Velocity",
+ value: "linvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "angvel",
+ },
+ {
+ text: "Translation (position)",
+ value: "translation",
+ },
+ {
+ text: "Rotation (quaternion)",
+ value: "rotation",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ //{text: "Center of Mass", value: "centerOfMass"},
+ {
+ text: "Linear Damping",
+ value: "linearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "angularDamping",
+ },
+ {
+ text: "Is Sleeping?",
+ value: "isSleeping",
+ },
+ //{text: "Can Sleep?", value: "isCanSleep"},
+ {
+ text: "Gravity Scale",
+ value: "gravityScale",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ //{text: "Sleeping", value: "sleeping"}
+ ],
+ },
+ rigidBodySets: {
+ acceptReporters: false,
+ items: [
+ //{text: "Linear Velocity", value: "setLinvel"},
+ //{text: "Angular Velocity", value: "setAngvel"},
+ //{text: "Mass", value: "setMass"},
+ {
+ text: "Gravity Scale",
+ value: "setGravityScale",
+ },
+ //{text: "Can Sleep?", value: "setCanSleep"},
+ //{text: "Sleeping", value: "sleeping"},
+ {
+ text: "Linear Damping",
+ value: "setLinearDamping",
+ },
+ {
+ text: "Angular Damping",
+ value: "setAngularDamping",
+ },
+ {
+ text: "Is Fixed?",
+ value: "isFixed",
+ },
+ {
+ text: "Is Dynamic?",
+ value: "isDynamic",
+ },
+ {
+ text: "Is Kinematic?",
+ value: "isKinematic",
+ },
+ ],
+ },
+ colliderProperties: {
+ acceptReporters: false,
+ items: [
+ //{text: "Collider Type", value: "type"},
+ {
+ text: "Is Sensor?",
+ value: "isSensor",
+ },
+ {
+ text: "Friction",
+ value: "friction",
+ },
+ {
+ text: "Restitution",
+ value: "restitution",
+ },
+ {
+ text: "Density",
+ value: "density",
+ },
+ {
+ text: "Mass",
+ value: "mass",
+ },
+ {
+ text: "Position",
+ value: "translation",
+ },
+ {
+ text: "Rotation",
+ value: "rotation",
+ },
+ //{text: "Area", value: "area"},
+ {
+ text: "Volume",
+ value: "volume",
+ },
+ {
+ text: "Collision Groups",
+ value: "collisionGroups",
+ },
+ //{text: "Collision Mask", value: "collisionMask"},
+ //{text: "Is Enabled?", value: "enabled"},
+ //{text: "Contact Count", value: "contactCount"},
+ //{text: "RigidBody Handle", value: "rigidBody"}
+ ],
+ },
+ colliderSets: {
+ acceptReporters: false,
+ items: [{
+ text: "Friction",
+ value: "setFriction",
+ },
+ {
+ text: "Restitution",
+ value: "setRestitution",
+ },
+ {
+ text: "Density",
+ value: "setDensity",
+ },
+ {
+ text: "Is Sensor?",
+ value: "setSensor",
+ },
+ {
+ text: "Collision Groups",
+ value: "setCollisionGroups",
+ },
+ //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
+ //{text: "Position Offset", value: "setTranslation"},
+ //{text: "Rotation Offset", value: "setRotation"}
+ ],
+ },
+ state: {
+ acceptReporters: true,
+ items: [{
+ text: "on",
+ value: "true",
+ },
+ {
+ text: "off",
+ value: "false",
+ },
+ ],
+ },
+ state2: {
+ acceptReporters: true,
+ items: [{
+ text: "false",
+ value: "false",
+ },
+ {
+ text: "true (must be fixed)",
+ value: "true",
+ },
+ ],
+ },
+ spaces: {
+ acceptReporters: false,
+ items: [{
+ text: "World",
+ value: "world",
+ },
+ {
+ text: "Local",
+ value: "local",
+ },
+ ],
+ },
+ objectTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Dynamic",
+ value: "dynamic",
+ },
+ {
+ text: "Fixed",
+ value: "fixed",
+ },
+ {
+ text: "Kinematic Position Based",
+ value: "kinematicPositionBased",
+ },
+ ],
+ },
+ colliderTypes: {
+ acceptReporters: false,
+ items: [{
+ text: "Box, Rectangle, cuboid",
+ value: "cuboid",
+ },
+ {
+ text: "Sphere, ball",
+ value: "ball",
+ },
+ {
+ text: "Custom, complex simple shapes, convexHull",
+ value: "convexHull",
+ },
+ {
+ text: "Precision, TriMesh",
+ value: "trimesh",
+ },
+ ],
+ },
+ forces: {
+ acceptReporters: false,
+ items: [{
+ text: "Force",
+ value: "addForce",
+ },
+ {
+ text: "Torque (rotation)",
+ value: "addTorque",
+ },
+ {
+ text: "Apply Impulse",
+ value: "applyImpulse",
+ },
+ {
+ text: "Apply Torque Impulse (rotation)",
+ value: "applyTorqueImpulse",
+ },
+ {
+ text: "Linear Velocity",
+ value: "setLinvel",
+ },
+ {
+ text: "Angular Velocity",
+ value: "setAngvel",
+ },
+ ],
+ },
+ resetF: {
+ acceptReporters: false,
+ items: [{
+ text: "Forces",
+ value: "resetForces",
+ },
+ {
+ text: "Torques",
+ value: "resetTorques",
+ },
+ ],
+ },
+ },
+ };
+ }
+ joint(jointData, bodyA, bodyB) {
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
+ }
+
+ fixedJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ let RA = JSON.parse(args.RA).map(Number);
+ let RB = JSON.parse(args.RB).map(Number);
+
+ RA = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RA[0]),
+ THREE.MathUtils.degToRad(RA[1]),
+ THREE.MathUtils.degToRad(RA[2])
+ )
+ );
+ RB = new THREE.Quaternion().setFromEuler(
+ new THREE.Euler(
+ THREE.MathUtils.degToRad(RB[0]),
+ THREE.MathUtils.degToRad(RB[1]),
+ THREE.MathUtils.degToRad(RB[2])
+ )
+ );
+
+ const data = RAPIER.JointData.fixed({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ },
+ RA, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ },
+ RB
+ );
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ sphericalJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+
+ const data = RAPIER.JointData.spherical({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ revoluteJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.revolute({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ prismaticJoint(args) {
+ const VA = JSON.parse(args.VA).map(Number);
+ const VB = JSON.parse(args.VB).map(Number);
+ const x = JSON.parse(args.X).map(Number);
+
+ const data = RAPIER.JointData.prismatic({
+ x: VA[0],
+ y: VA[1],
+ z: VA[2],
+ }, {
+ x: VB[0],
+ y: VB[1],
+ z: VB[2],
+ }, {
+ x: x[0],
+ y: x[1],
+ z: x[2],
+ });
+ const objectA = getObject(args.ObjA);
+ let object = getObject(args.ObjB);
+ this.joint(data, objectA, object);
+ }
+
+ createWorld(args) {
+ const v3 = JSON.parse(args.G).map(Number);
+ const gravity = {
+ x: v3[0],
+ y: v3[1],
+ z: v3[2],
+ };
+ physicsWorld = new RAPIER.World(gravity);
+
+ console.log(physicsWorld);
+ }
+
+ getWorld(args) {
+ if (args.PROPERTY === "log") {
+ console.log(physicsWorld);
+ return "logged";
+ }
+ return JSON.stringify(physicsWorld[args.PROPERTY]);
+ }
+
+ setRB(args) {
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.rigidBody[args.PROPERTY](value);
+ }
+ setC(args) {
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.collider[args.PROPERTY](value);
+ }
+
+ getRB(args) {
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.rigidBody[args.PROPERTY]());
+ }
+ getC(args) {
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.collider[args.PROPERTY]());
+ }
+
+ lockObjectAxis(args) {
+ let object = getObject(args.OBJECT);
+ const x = !JSON.parse(args.X);
+ const y = !JSON.parse(args.Y);
+ const z = !JSON.parse(args.Z);
+ object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
+ }
+
+ objectPhysics(args) {
+ let object = getObject(args.OBJECT);
+ object.physics = JSON.parse(args.state);
+
+ if (JSON.parse(args.state)) {
+ //if already exists delete:
+ if (object.rigidBody) {
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ /*asing a rigidbody and collider to object and add them to physicsWorld*/
+ let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
+ .setTranslation(object.position.x, object.position.y, object.position.z)
+ .setRotation({
+ w: object.quaternion._w,
+ x: object.quaternion._x,
+ y: object.quaternion._y,
+ z: object.quaternion._z,
+ });
+
+ let colliderDesc;
+ switch (args.collider) {
+ case "cuboid":
+ colliderDesc = createCuboidCollider(object);
+ break;
+ case "ball":
+ colliderDesc = createBallCollider(object);
+ break;
+ case "convexHull":
+ colliderDesc = createConvexHullCollider(object);
+ break;
+ case "trimesh":
+ colliderDesc = TriMesh(object);
+ break;
+ }
+ colliderDesc
+ .setSensor(JSON.parse(args.state2))
+ .setMass(args.mass)
+ .setDensity(args.density)
+ .setFriction(args.friction);
+
+ let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
+ let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
+
+ object.rigidBody = rigidBody;
+ object.collider = collider;
+ } else {
+ /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
+ }
+ }
+
+ enableCCD(args) {
+ let object = getObject(args.OBJECT);
+ if (object.physics) {
+ let rigidBody = object.rigidBody;
+ rigidBody.enableCcd(JSON.parse(args.state));
+ }
+ }
+
+ addForce(args) {
+ let object = getObject(args.OBJECT);
+ const vector = JSON.parse(args.VALUE).map(Number);
+
+ let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
+ if (args.SPACE === "local") {
+ force.applyQuaternion(object.quaternion);
+ }
+
+ object.rigidBody[args.PROPERTY](force, true);
+ }
+
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true);
+ }
+
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR);
+
+ let object = getObject(args.OBJECT);
+
+ let touching = false;
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ if (otherCollider === object.collider) touching = true;
+ });
+
+ return touching;
+ }
+
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR);
+
+ const touchedObjects = [];
+
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ // find owner of collider
+ const otherObject = scene.children.find((o) => o.collider === otherCollider);
+ console.log(otherCollider);
+ if (otherObject) touchedObjects.push(otherObject.name);
+ });
+
+ return JSON.stringify(touchedObjects);
+ }
+ }
+ Scratch.extensions.register(new RapierPhysics());
+
+ //Thanks to the PointerLock extension of Turbowarp
+ const mouse = vm.runtime.ioDevices.mouse;
+ let isLocked = false;
+ let isPointerLockEnabled = false;
+
+ let rect = threeRenderer.domElement.getBoundingClientRect();
+ document.addEventListener("resize", () => {
+ rect = threeRenderer.domElement.getBoundingClientRect();
+ });
+
+ const postMouseData = (e, isDown) => {
+ const {
+ movementX,
+ movementY
+ } = e;
+ const {
+ width,
+ height
+ } = rect;
+ const x = mouse._clientX + movementX;
+ const y = mouse._clientY - movementY;
+ mouse._clientX = x;
+ mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
+ mouse._clientY = y;
+ mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
+ if (typeof isDown === "boolean") {
+ const data = {
+ button: e.button,
+ isDown,
+ };
+ originalPostIOData(data);
+ }
+ };
+
+ const mouseDevice = vm.runtime.ioDevices.mouse;
+ const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
+ mouseDevice.postData = (data) => {
+ if (!isPointerLockEnabled) {
+ return originalPostIOData(data);
+ }
+ };
+
+ document.addEventListener(
+ "mousedown",
+ (e) => {
+ // @ts-expect-error
+ if (threeRenderer.domElement.contains(e.target)) {
+ if (isLocked) {
+ postMouseData(e, true);
+ } else if (isPointerLockEnabled) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mouseup",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e, false);
+ // @ts-expect-error
+ } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
+ threeRenderer.domElement.requestPointerLock();
+ }
+ },
+ true
+ );
+ document.addEventListener(
+ "mousemove",
+ (e) => {
+ if (isLocked) {
+ postMouseData(e);
+ }
+ },
+ true
+ );
+
+ document.addEventListener("pointerlockchange", () => {
+ isLocked = document.pointerLockElement === threeRenderer.domElement;
+ });
+ document.addEventListener("pointerlockerror", (e) => {
+ console.error("Pointer lock error", e);
+ });
+
+ const oldStep = vm.runtime._step;
+ vm.runtime._step = function(...args) {
+ const ret = oldStep.call(this, ...args);
+ if (isPointerLockEnabled) {
+ const {
+ width,
+ height
+ } = rect;
+ mouse._clientX = width / 2;
+ mouse._clientY = height / 2;
+ mouse._scratchX = 0;
+ mouse._scratchY = 0;
+ }
+ return ret;
+ };
+
+ vm.runtime.on("PROJECT_LOADED", () => {
+ isPointerLockEnabled = false;
+ if (isLocked) {
+ document.exitPointerLock();
+ }
+ });
+
+ class Pointerlock {
+ getInfo() {
+ return {
+ id: "threepointerlockmod",
+ name: "Pointerlock for Extra 3D",
+ color1: "#8a8a8aff",
+ color2: "#222222",
+ color3: "#222222",
+
+ blocks: [{
+ opcode: "setLocked",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set pointer lock [enabled]",
+ arguments: {
+ enabled: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "true",
+ menu: "enabled",
+ },
+ },
+ },
+ {
+ opcode: "isLocked",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "pointer locked?",
+ },
+ ],
+ menus: {
+ enabled: {
+ acceptReporters: true,
+ items: [{
+ text: "enabled",
+ value: "true",
+ },
+ {
+ text: "disabled",
+ value: "false",
+ },
+ ],
+ },
+ },
+ };
+ }
+
+ setLocked(args) {
+ isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
+ if (!isPointerLockEnabled && isLocked) {
+ document.exitPointerLock();
+ }
+ }
+
+ isLocked() {
+ return isLocked;
+ }
+ }
+ Scratch.extensions.register(new Pointerlock());
+ });
+})(Scratch);
From a7b3332b9bbdb9e2e396d06d7269e0d5f79b29e9 Mon Sep 17 00:00:00 2001
From: FreshPenguin112
Date: Tue, 16 Dec 2025 17:00:26 -0600
Subject: [PATCH 14/32] merge
---
threejsD.js.orig | 5029 --------------------------------------
threejsD_BACKUP_13852.js | 5029 --------------------------------------
threejsD_BASE_13852.js | 2413 ------------------
threejsD_LOCAL_13852.js | 2414 ------------------
threejsD_REMOTE_13852.js | 5016 -------------------------------------
5 files changed, 19901 deletions(-)
delete mode 100644 threejsD.js.orig
delete mode 100644 threejsD_BACKUP_13852.js
delete mode 100644 threejsD_BASE_13852.js
delete mode 100644 threejsD_LOCAL_13852.js
delete mode 100644 threejsD_REMOTE_13852.js
diff --git a/threejsD.js.orig b/threejsD.js.orig
deleted file mode 100644
index d1db442..0000000
--- a/threejsD.js.orig
+++ /dev/null
@@ -1,5029 +0,0 @@
-/* jshint esversion: 11 */
-// Name: Extra 3D
-// ID: threejsExtension
-// Description: Use three js inside Turbowarp! A 3D graphics library.
-// By: Civero
-// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
-
-(function(Scratch) {
- "use strict";
-
- if (!Scratch.extensions.unsandboxed) {
- throw new Error("Three-D extension must run unsandboxed");
- }
-
- if (Scratch.vm.runtime.isPackaged) {
- alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
- return;
- }
- //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
-
- const vm = Scratch.vm;
- const runtime = vm.runtime;
- const renderer = Scratch.renderer;
- const canvas = renderer.canvas;
- const Cast = Scratch.Cast;
- const menuIconURI =
- "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
-
- let alerts = false;
- console.log("alerts are " + (alerts ? "enabled" : "disabled"));
-
- let isMouseDown = {
- left: false,
- middle: false,
- right: false,
- };
- let prevMouse = {
- left: false,
- middle: false,
- right: false,
- };
-
- let lastWidth = 0;
- let lastHeight = 0;
-
- let THREE;
- let clock;
- let running;
- let loopId;
- //Addons
- let GLTFLoader;
- let gltf;
- let OrbitControls;
- let controls;
- let BufferGeometryUtils;
- let TextGeometry;
- let fontLoad;
- //Physics
- let RAPIER;
- let physicsWorld;
-
- let threeRenderer;
- let scene;
- let camera;
- let eulerOrder = "YXZ";
-
- let composer;
- let passes = {};
- let customEffects = [];
- let renderTargets = {};
-
- let materials = {};
- let geometries = {};
- let lights = {};
- let models = {};
-
- let assets = {
- //should i place materials, geometries; inside too?
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {}, //not the same as the global one! this one only stores textures
- };
-
- let raycastResult = [];
-
- function resetor(level) {
- camera = undefined;
- composer.reset();
-
- passes = {};
- customEffects = [];
- renderTargets = {};
-
- materials = {};
- geometries = {};
- lights = {};
- models = {};
-
- if (level > 0) {
- assets = {
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {},
- };
- }
-
- updateComposers();
- }
-
- //utility
- function vector3ToString(prop) {
- if (!prop) return "0,0,0";
-
- const x =
- typeof prop.x === "number" ?
- prop.x :
- typeof prop._x === "number" ?
- prop._x :
- JSON.stringify(prop).includes("X") ?
- prop :
- 0;
- const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
- const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
-
- return [x, y, z];
- }
-
- //objects
- function createObject(name, content, parentName) {
- let object = getObject(name, true);
- if (object) {
- removeObject(name);
- alerts ? alert(name + " already exsisted, will replace!") : null;
- }
- content.name = name;
- content.rotation._order = eulerOrder;
- parentName === scene.name ? (object = scene) : (object = getObject(parentName));
- content.physics = false;
-
- object.add(content);
- }
-
- function removeObject(name) {
- let object = getObject(name);
- if (!object) return;
-
- scene.remove(object);
-
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true);
- physicsWorld.removeRigidBody(object.rigidBody, true);
- object.rigidBody = null;
- object.collider = null;
- }
- if (object.isLight) {
- delete lights[name];
- }
- }
-
- function getObject(name, isNew) {
- let object = null;
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
- return;
- }
- object = scene.getObjectByName(name);
- if (!object && !isNew) {
- alerts ? alert(name + " does not exist! Add it to scene") : null;
- return;
- }
- return object;
- }
-
- //materials
- function encodeCostume(name) {
- if (name.startsWith("data:image/")) return name;
- return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
- }
-
- function setTexutre(texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace;
-
- if (mode === "Pixelate") {
- texture.minFilter = THREE.NearestFilter;
- texture.magFilter = THREE.NearestFilter;
- } else {
- //Blur
- texture.minFilter = THREE.NearestMipmapLinearFilter;
- texture.magFilter = THREE.NearestMipmapLinearFilter;
- }
-
- if (style === "Repeat") {
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- texture.repeat.set(x, y);
- }
-
- texture.generateMipmaps = true;
- }
- async function resizeImageToSquare(uri, size = 256) {
- return new Promise((resolve) => {
- const img = new Image();
- img.onload = () => {
- const canvas = document.createElement("canvas");
- canvas.width = size;
- canvas.height = size;
- const ctx = canvas.getContext("2d");
-
- // clear + draw image scaled to fit canvas
- ctx.clearRect(0, 0, size, size);
- ctx.drawImage(img, 0, 0, size, size);
-
- resolve(canvas.toDataURL()); // return normalized Data URI
- //delete canvas?
- };
- img.src = uri;
- });
- }
- //light
- function updateShadowFrustum(light, focusPos) {
- if (light.type !== "DirectionalLight") return;
-
- // Frustum Size - Increase this value to cover a larger area.
- const d = 50;
-
- // Update Orthographic Shadow Camera Frustum
- const shadowCamera = light.shadow.camera;
-
- // Set the width/height of the frustum
- shadowCamera.left = -d;
- shadowCamera.right = d;
- shadowCamera.top = d;
- shadowCamera.bottom = -d;
-
- // Determine ranges
- shadowCamera.near = 0.1;
- shadowCamera.far = 500;
-
- // Position the Light and its Target
- light.target.position.copy(focusPos);
- const direction = light.position.clone().sub(light.target.position).normalize();
- light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
-
- // Ensure matrices are updated.
- light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true;
- }
- //composer
- function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
-
- // always recreate the RenderPass to point to the current scene/camera
- passes["Render"] = new RenderPass(scene, camera);
-
- // ensure composer has a RenderPass as the first pass
- const hasRender = composer.passes.some((p) => p && p.scene);
- if (!hasRender) composer.addPass(passes["Render"]);
- else {
- // if composer already has one, replace it so it references current scene/camera
- const idx = composer.passes.findIndex((p) => p && p.scene);
- composer.passes[idx] = passes["Render"];
- }
- }
- //utility
- function getMouseNDC(event) {
- // Use threeRenderer.domElement for correct offset
- const rect = threeRenderer.domElement.getBoundingClientRect();
- const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
- const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
- return [x, y];
- }
-
- function checkCanvasSize() {
- const {
- width,
- height
- } = canvas;
- if (width !== lastWidth || height !== lastHeight) {
- lastWidth = width;
- lastHeight = height;
- resize();
- }
- requestAnimationFrame(checkCanvasSize); //rerun next frame
- }
- //physics
- function computeWorldBoundingBox(mesh) {
- // Create a Box3 in world coordinates
- const box = new THREE.Box3().setFromObject(mesh);
- const size = new THREE.Vector3();
- box.getSize(size);
- const center = new THREE.Vector3();
- box.getCenter(center);
- return {
- size,
- center,
- };
- }
-
- function createCuboidCollider(mesh) {
- const {
- size
- } = computeWorldBoundingBox(mesh);
- const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
- return collider;
- }
-
- function createBallCollider(mesh) {
- const {
- size
- } = computeWorldBoundingBox(mesh);
- // radius = 1/2 of the largest verticie
- const radius = Math.max(size.x, size.y, size.z) / 2;
- const collider = RAPIER.ColliderDesc.ball(radius);
- return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
- }
-
- function createConvexHullCollider(mesh) {
- mesh.updateWorldMatrix(true, false);
-
- const position = mesh.geometry.attributes.position;
- const vertices = [];
- const vertex = new THREE.Vector3();
-
- // Matrix for scale only
- const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
-
- for (let i = 0; i < position.count; i++) {
- vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
- vertices.push(vertex.x, vertex.y, vertex.z);
- }
-
- const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
- return collider;
- }
-
- function TriMesh(mesh) {
- // Get the positions array (from your geoPoints function)
- const positions = mesh.geometry.attributes.position.array;
- const numVertices = positions.length / 3;
-
- // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
- const indices = Array.from({
- length: numVertices,
- },
- (_, i) => i
- );
-
- const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
-
- return collider;
- }
-
- function getModel(model, name) {
- const file = runtime
- .getTargetForStage()
- .getSounds()
- .find((c) => c.name === model);
- if (!file) return;
-
- return new Promise((resolve, reject) => {
- gltf.parse(
- file.asset.data.buffer,
- "",
- (gltf) => {
- const root = gltf.scene;
- root.traverse((child) => {
- if (child.isMesh) {
- child.castShadow = true;
- child.receiveShadow = true;
- }
- });
-
- const mixer = new THREE.AnimationMixer(root);
- const actions = {};
- gltf.animations.forEach((clip) => {
- const act = mixer.clipAction(clip);
- act.clampWhenFinished = true;
- actions[clip.name] = act;
- });
-
- models[name] = {
- root,
- mixer,
- actions,
- };
- resolve(root);
- },
- (error) => {
- console.error("Error parsing GLB model:", error);
- reject(error);
- }
- );
- });
- }
- async function openFileExplorer(format) {
- return new Promise((resolve) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = format;
- input.multiple = false;
- input.onchange = () => {
- resolve(input.files);
- input.remove();
- };
- input.click();
- });
- }
-
- function getMeshesUsingTexture(scene, targetTexture) {
- const meshes = [];
-
- scene.traverse((object) => {
- if (object.material) {
- const materials = Array.isArray(object.material) ? object.material : [object.material];
- for (const material of materials) {
- if (material.map === targetTexture) {
- meshes.push(object);
- break;
- }
- }
- }
- });
-
- return meshes;
- }
-
- function getAsset(path) {
- if (typeof path == "string") {
- //string?
- if (path.includes("/")) {
- //has the /?
- const value = path.split("/");
- console.log(value[0], value[1]);
- return assets[value[0]][value[1]];
- }
- }
-
- return JSON.parse(path); //boolean or number
- }
-
- let mouseNDC = [0, 0];
- //loops/init
- function stopLoop() {
- if (!running) return;
- running = false;
-
- if (loopId) {
- cancelAnimationFrame(loopId);
- loopId = null;
- if (threeRenderer) threeRenderer.clear();
- }
- }
- async function load() {
- if (!THREE) {
- // @ts-ignore
-<<<<<<< HEAD
- THREE = await import("https://esm.sh/three@0.180.0")
- window._THREE_ = THREE
-=======
- THREE = await import("https://esm.sh/three@0.180.0");
->>>>>>> e4a038b (Update threejsD.js)
- //Addons
- GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
- OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
- BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
- TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
- const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
- fontLoad = new FontLoader.FontLoader();
-
- const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
- const {
- EffectComposer,
- EffectPass,
- RenderPass,
-
- Effect,
- BloomEffect,
- GodRaysEffect,
- DotScreenEffect,
- DepthOfFieldEffect,
-
- BlendFunction,
- } = POSTPROCESSING;
- //so i can use them later as global
- window.EffectComposer = EffectComposer;
- window.EffectPass = EffectPass;
- window.RenderPass = RenderPass;
- window.Effect = Effect;
- window.BloomEffect = BloomEffect;
- window.GodRaysEffect = GodRaysEffect;
- window.DotScreenEffect = DotScreenEffect;
- window.DepthOfFieldEffect = DepthOfFieldEffect;
- window.BlendFunction = BlendFunction;
-
- RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
- await RAPIER.init();
-
- threeRenderer = new THREE.WebGLRenderer({
- powerPreference: "high-performance",
- antialias: false,
- stencil: false,
- depth: true,
- });
- threeRenderer.setPixelRatio(window.devicePixelRatio);
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
- //threeRenderer.toneMappingExposure = 1.0 //(test)
-
- threeRenderer.shadowMap.enabled = true;
- threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
- threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
-
- gltf = new GLTFLoader.GLTFLoader();
- clock = new THREE.Clock();
-
- // Example: create a composer
- composer = new EffectComposer(threeRenderer, {
- frameBufferType: THREE.HalfFloatType,
- });
-
- renderer.addOverlay(threeRenderer.domElement, "manual");
- renderer.addOverlay(canvas, "manual");
- renderer.setBackgroundColor(1, 1, 1, 0);
-
- resize();
-
- window.addEventListener("mousedown", (e) => {
- if (e.button === 0) isMouseDown.left = true;
- if (e.button === 1) isMouseDown.middle = true;
- if (e.button === 2) isMouseDown.right = true;
- });
- window.addEventListener("mouseup", (e) => {
- if (e.button === 0) isMouseDown.left = false;
- prevMouse.left = false;
- if (e.button === 1) isMouseDown.middle = false;
- prevMouse.middle = false;
- if (e.button === 2) isMouseDown.right = false;
- prevMouse.right = false;
- });
- // prevent contextmenu on right click
- threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
-
- threeRenderer.domElement.addEventListener("mousemove", (event) => {
- mouseNDC = getMouseNDC(event);
- });
-
- running = false;
- load();
-
-<<<<<<< HEAD
- startRenderLoop()
- runtime.on('PROJECT_START', () => startRenderLoop())
- runtime.on('PROJECT_STOP_ALL', () => stopLoop())
- runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
- checkCanvasSize()
-=======
- startRenderLoop();
- runtime.on("PROJECT_START", () => startRenderLoop());
- runtime.on("PROJECT_STOP_ALL", () => stopLoop());
- runtime.on("STAGE_SIZE_CHANGED", () => {
- requestAnimationFrame(() => resize());
- });
- //if (!runtime.isPackaged) checkCanvasSize() //only in editor
->>>>>>> e4a038b (Update threejsD.js)
- }
- }
-
- function startRenderLoop() {
- if (running) return;
- running = true;
-
- const loop = () => {
- if (!running) return;
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step();
-
- scene.children.forEach((obj) => {
- if (!obj.isMesh || !obj.physics) return;
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation());
- obj.quaternion.copy(obj.rigidBody.rotation());
- }
- });
- }
- if (scene && camera) {
- if (controls) controls.update();
-
- const delta = clock.getDelta();
- Object.values(models).forEach((model) => {
- if (model) model.mixer.update(delta);
- });
-
- Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
-
- //update custom effects time
- customEffects.forEach((e) => {
- if (e.uniforms.get("time")) {
- e.uniforms.get("time").value += delta;
- }
- });
- Object.values(renderTargets).forEach((t) => {
- if (t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height;
- t.camera.updateProjectionMatrix();
- }
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = false;
- });
-
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target);
- threeRenderer.clear(true, true, true);
- threeRenderer.render(scene, t.camera);
- } else {
- t.target.clear(threeRenderer);
- t.camera.update(threeRenderer, scene); //cubeCamera
- }
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = true;
- });
- });
-
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
- camera.updateProjectionMatrix();
- threeRenderer.setRenderTarget(null);
- composer.render(delta);
- }
-
- loopId = requestAnimationFrame(loop);
- };
-
- loopId = requestAnimationFrame(loop);
- }
-
- function resize() {
- const w = canvas.width;
- const h = canvas.height;
-
- threeRenderer.setSize(w, h);
- composer.setSize(w, h);
- customEffects.forEach((e) => {
- if (e.uniforms.get("resolution")) {
- e.uniforms.get("resolution").value.set(w, h);
- }
- });
-
- if (camera) {
- camera.aspect = w / h;
- camera.updateProjectionMatrix();
- }
- }
- //wait until all packages are loaded
- Promise.resolve(load()).then(() => {
- class threejsExtension {
- getInfo() {
- return {
- id: "threejsExtension",
- name: "Extra 3D",
- color1: "#222222",
- color2: "#222222",
- color3: "#11cc99",
- menuIconURI,
- blockIconURI: menuIconURI,
-
- blocks: [{
- blockType: Scratch.BlockType.BUTTON,
- text: "Show Docs",
- func: "openDocs",
- },
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Toggle Alerts",
- func: "alerts",
- },
- ],
- menus: {},
- };
- }
- openDocs() {
- open("https://civ3ro.github.io/extensions/Documentation/");
- }
- alerts() {
- alerts = !alerts;
- alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
- }
- }
- Scratch.extensions.register(new threejsExtension());
-
- class ThreeRenderer {
- getInfo() {
- return {
- id: "threeRenderer",
- name: "Three Renderer",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "setRendererRatio",
- blockType: Scratch.BlockType.COMMAND,
- text: "set Pixel Ratio to [VALUE]",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "eulerOrder",
- blockType: Scratch.BlockType.COMMAND,
- text: "set euler order to [VALUE]",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "YXZ",
- },
- },
- },
- ],
- menus: {},
- };
- }
-
- setRendererRatio(args) {
- threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
- }
- eulerOrder(args) {
- eulerOrder = args.VALUE;
- console.log("euler order set to", eulerOrder);
- }
- }
- Scratch.extensions.register(new ThreeRenderer());
-
- class ThreeScene {
- constructor() {
- this.THREE = THREE;
- this.scenes = {};
- }
-
- getInfo() {
- return {
- id: "threeScene",
- name: "Three Scene",
- color1: "#4638c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "newScene",
- blockType: Scratch.BlockType.COMMAND,
- text: "new Scene [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- },
- },
-
- {
- opcode: "setSceneProperty",
- blockType: Scratch.BlockType.COMMAND,
- text: "set Scene [PROPERTY] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "sceneProperties",
- defaultValue: "background",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "new Color()",
- exemptFromNormalization: true,
- },
- },
- },
- "---",
- {
- opcode: "getSceneObjects",
- blockType: Scratch.BlockType.REPORTER,
- text: "get Scene [THING]",
- arguments: {
- THING: {
- type: Scratch.ArgumentType.STRING,
- menu: "sceneThings",
- },
- },
- },
- {
- opcode: "reset",
- blockType: Scratch.BlockType.COMMAND,
- text: "Reset Everything",
- },
- ],
- menus: {
- sceneProperties: {
- acceptReporters: false,
- items: [{
- text: "Background",
- value: "background",
- },
- {
- text: "Background Blurriness",
- value: "backgroundBlurriness",
- },
- {
- text: "Background Intensity",
- value: "backgroundIntensity",
- },
- {
- text: "Background Rotation",
- value: "backgroundRotation",
- },
- {
- text: "Environment",
- value: "environment",
- },
- {
- text: "Environment Intensity",
- value: "environmentIntensity",
- },
- {
- text: "Environment Rotation",
- value: "environmentRotation",
- },
- {
- text: "Fog",
- value: "fog",
- },
- ],
- },
- sceneThings: {
- acceptReporters: false,
- items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
- },
- },
- };
- }
-
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME;
- scene.background = new THREE.Color("#222");
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {
- ...this.scenes,
- ...scene,
- };
- resetor(0);
- }
-
- reset() {
- resetor(1);
- }
-
- async setSceneProperty(args) {
- const property = args.PROPERTY;
- const value = getAsset(args.VALUE);
-
- scene[property] = value;
- }
- getSceneObjects(args) {
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse((obj) => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
- else if (args.THING === "Scene Properties") {
- console.log(scene);
- return "check console";
- } else if (args.THING === "Other assets") return JSON.stringify(assets);
-
- return JSON.stringify(names); // if objects
- }
- }
- Scratch.extensions.register(new ThreeScene());
-
- class ThreeCameras {
- getInfo() {
- return {
- id: "threeCameras",
- name: "Three Cameras",
- color1: "#38c59bff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "add camera [TYPE] [CAMERA] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraTypes",
- },
- },
- },
- {
- opcode: "setCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraProperties",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "0.1",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "getCamera",
- blockType: Scratch.BlockType.REPORTER,
- text: "get camera [PROPERTY] of [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraProperties",
- },
- },
- },
- "---",
- {
- opcode: "renderSceneCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "set rendering camera to [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- },
- },
- "---",
- {
- opcode: "cubeCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "cubeCamera",
- },
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- "---",
- {
- opcode: "renderTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "set a RenderTarget: [RT] for camera [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- {
- opcode: "sizeTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "set RenderTarget [RT] size to [W] [H]",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- W: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 480,
- },
- H: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 360,
- },
- },
- },
- {
- opcode: "getTarget",
- blockType: Scratch.BlockType.REPORTER,
- text: "get RenderTarget: [RT] texture",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- {
- opcode: "removeTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "remove RenderTarget: [RT]",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- ],
- menus: {
- cameraTypes: {
- acceptReporters: false,
- items: [{
- text: "Perspective",
- value: "PerspectiveCamera",
- }, ],
- },
- cameraProperties: {
- acceptReporters: false,
- items: [{
- text: "Near",
- value: "near",
- },
- {
- text: "Far",
- value: "far",
- },
- {
- text: "FOV",
- value: "fov",
- },
- {
- text: "Focus (nothing...)",
- value: "focus",
- },
- {
- text: "Zoom",
- value: "zoom",
- },
- ],
- },
- },
- };
- }
- addCamera(args) {
- let v2 = new THREE.Vector2();
- threeRenderer.getSize(v2);
- const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
- object.position.z = 3;
-
- createObject(args.CAMERA, object, args.GROUP);
- }
- setCamera(args) {
- let object = getObject(args.CAMERA);
- object[args.PROPERTY] = args.VALUE;
- object.updateProjectionMatrix();
- }
- getCamera(args) {
- let object = getObject(args.CAMERA);
- const value = JSON.stringify(object[args.PROPERTY]);
- return value;
- }
- renderSceneCamera(args) {
- let object = getObject(args.CAMERA);
- if (!object) return;
- camera = object;
- //reset composer, else it does not update.
- composer.passes = [];
- passes = {};
- customEffects = [];
- updateComposers();
- }
-
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
- generateMipmaps: true,
- });
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
- createObject(args.CAMERA, cubeCamera, args.GROUP);
-
- renderTargets[args.RT] = {
- target: cubeRenderTarget,
- camera: cubeCamera,
- };
- assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
- }
-
- renderTarget(args) {
- let object = getObject(args.CAMERA);
- const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
- generateMipmaps: false,
- });
-
- renderTargets[args.RT] = {
- target: renderTarget,
- camera: object,
- };
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
- }
- sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H);
- }
- getTarget(args) {
- const t = renderTargets[args.RT].target.texture;
- console.log(t, renderTargets[args.RT]);
- return `renderTargets/${t.uuid}`;
- }
- removeTarget(args) {
- delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
- renderTargets[args.RT].target.dispose();
- delete renderTargets[args.RT];
- }
- }
- Scratch.extensions.register(new ThreeCameras());
-
- class ThreeObjects {
- getInfo() {
- return {
- id: "threeObjects",
- name: "Three Objects",
- color1: "#38c567ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "add object [OBJECT3D] [TYPE] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectTypes",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "cloneObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myClone",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "setObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectProperties",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- {
- opcode: "getObject",
- blockType: Scratch.BlockType.REPORTER,
- text: "get [PROPERTY] of object [OBJECT3D]",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectProperties",
- },
- },
- },
- {
- opcode: "objectE",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there an object [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "removeObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "remove object [OBJECT3D] from scene",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: " ↳ Transforms",
- },
- {
- opcode: "setObjectV3",
- extensions: ["colours_motion"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectVector3",
- defaultValue: "position",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
- //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {
- opcode: "getObjectV3",
- extensions: ["colours_motion"],
- blockType: Scratch.BlockType.REPORTER,
- text: "get [PROPERTY] of [OBJECT3D]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectVector3",
- defaultValue: "position",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Materials",
- },
- {
- opcode: "newMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new material [NAME] [TYPE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "materialTypes",
- defaultValue: "MeshStandardMaterial",
- },
- },
- },
- {
- opcode: "materialE",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there a material [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- },
- },
- {
- opcode: "removeMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "remove material [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- },
- },
- {
- opcode: "setMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [PROPERTY] of [NAME] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "materialProperties",
- defaultValue: "color",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "new Color()",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "setBlending",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [NAME] blending to [VALUE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- },
- },
- },
- {
- opcode: "setDepth",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [NAME] depth to [VALUE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- menu: "depthModes",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Geometries",
- },
- {
- opcode: "newGeometry",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new geometry [NAME] [TYPE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "geometryTypes",
- defaultValue: "BoxGeometry",
- },
- },
- },
- {
- opcode: "geometryE",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there a geometry [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- {
- opcode: "removeGeometry",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "remove geometry [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- "---",
- {
- opcode: "newGeo",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new empty geometry [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[points]",
- },
- },
- },
- {
- opcode: "geoPoints",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set geometry [NAME] vertex points to [POINTS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[points]",
- },
- },
- },
- {
- opcode: "geoUVs",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set geometry [NAME] UVs to [POINTS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[UVs]",
- },
- },
- },
- "---",
- {
- opcode: "splines",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create spline [NAME] from curve [CURVE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySpline",
- },
- CURVE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[curve]",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "splineModel",
- extensions: ["colours_operators"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySpline",
- },
- MODEL: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelsList",
- },
- CURVE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[curve]",
- exemptFromNormalization: true,
- },
- SPACING: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Convert font to JSON",
- func: "openConv",
- },
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Load JSON font file",
- func: "loadFont",
- },
- {
- opcode: "text",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myText",
- },
- TEXT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "C-369",
- },
- FONT: {
- type: Scratch.ArgumentType.STRING,
- menu: "fonts",
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- D: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.1,
- },
- CS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 6,
- },
- },
- },
- ],
- menus: {
- objectVector3: {
- acceptReporters: false,
- items: [{
- text: "Positon",
- value: "position",
- },
- {
- text: "Rotation",
- value: "rotation",
- },
- {
- text: "Scale",
- value: "scale",
- },
- {
- text: "Facing Direction (.up)",
- value: "up",
- },
- ],
- },
- objectProperties: {
- acceptReporters: false,
- items: [{
- text: "Geometry",
- value: "geometry",
- },
- {
- text: "Material",
- value: "material",
- },
- {
- text: "Visible (true/false)",
- value: "visible",
- },
- ],
- },
- objectTypes: {
- acceptReporters: false,
- items: [{
- text: "Mesh",
- value: "Mesh",
- },
- {
- text: "Sprite",
- value: "Sprite",
- },
- {
- text: "Points",
- value: "Points",
- },
- {
- text: "Line",
- value: "Line",
- },
- {
- text: "Group",
- value: "Group",
- },
- ],
- },
- XYZ: {
- acceptReporters: false,
- items: [{
- text: "X",
- value: "x",
- },
- {
- text: "Y",
- value: "y",
- },
- {
- text: "Z",
- value: "z",
- },
- ],
- },
- materialProperties: {
- acceptReporters: false,
- items: [
- "|GENERAL| <-- not a property",
- {
- text: "Color",
- value: "color",
- },
- {
- text: "Map",
- value: "map",
- },
- {
- text: "Opacity",
- value: "opacity",
- },
- {
- text: "Transparent",
- value: "transparent",
- },
- {
- text: "Alpha Map",
- value: "alphaMap",
- },
- {
- text: "Alpha Test",
- value: "alphaTest",
- },
- {
- text: "Depth Test",
- value: "depthTest",
- },
- {
- text: "Depth Write",
- value: "depthWrite",
- },
- {
- text: "Color Write",
- value: "colorWrite",
- },
- {
- text: "Side",
- value: "side",
- },
- {
- text: "Visible",
- value: "visible",
- },
- /*
- { text: "Blending", value: "blending" },
- { text: "Blend Src", value: "blendSrc" },
- { text: "Blend Dst", value: "blendDst" },
- { text: "Blend Equation", value: "blendEquation" },
- { text: "Blend Src Alpha", value: "blendSrcAlpha" },
- { text: "Blend Dst Alpha", value: "blendDstAlpha" },
- { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
- {
- text: "Blend Aplha",
- value: "blendAplha",
- },
- {
- text: "Blend Color",
- value: "blendColor",
- },
- {
- text: "Alpha Hash",
- value: "alphaHash",
- },
- {
- text: "Premultiplied Alpha",
- value: "premultipliedAlpha",
- },
-
- {
- text: "Tone Mapped",
- value: "toneMapped",
- },
- {
- text: "Fog",
- value: "fog",
- },
- {
- text: "Flat Shading",
- value: "flatShading",
- },
-
- "|MESH Standard / Physical| <-- not a property",
- {
- text: "Metalness",
- value: "metalness",
- },
- {
- text: "Metalness Map",
- value: "metalnessMap",
- },
- {
- text: "Roughness",
- value: "roughness",
- },
- {
- text: "Reflectivity",
- value: "reflectivity",
- },
- {
- text: "Roughness Map",
- value: "roughnessMap",
- },
- {
- text: "Emissive",
- value: "emissive",
- },
- {
- text: "Emissive Intensity",
- value: "emissiveIntensity",
- },
- {
- text: "Emissive Map",
- value: "emissiveMap",
- },
- {
- text: "Env Map",
- value: "envMap",
- },
- {
- text: "Env Map Intensity",
- value: "envMapIntensity",
- },
- {
- text: "Env Map Rotation",
- value: "envMapRotation",
- },
- {
- text: "Ior",
- value: "ior",
- },
- {
- text: "Refraction Ratio",
- value: "refractionRatio",
- },
- {
- text: "Clearcoat",
- value: "clearcoat",
- },
- {
- text: "Clearcoat Map",
- value: "clearcoatMap",
- },
- {
- text: "Clearcoat Roughness",
- value: "clearcoatRoughness",
- },
- {
- text: "Clearcoat Roughness Map",
- value: "clearcoatRoughnessMap",
- },
- {
- text: "Dispersion",
- value: "dispersion",
- },
- {
- text: "Sheen",
- value: "sheen",
- },
- {
- text: "Sheen Color",
- value: "sheenColor",
- },
- {
- text: "Sheen Color Map",
- value: "sheenColorMap",
- },
- {
- text: "Sheen Roughness",
- value: "sheenRoughness",
- },
- {
- text: "Sheen Roughness Map",
- value: "sheenRoughnessMap",
- },
- {
- text: "Specular Color",
- value: "specularColor",
- },
- {
- text: "Specular Color Map",
- value: "specularColorMap",
- },
- {
- text: "Specular Intensity",
- value: "specularIntensity",
- },
- {
- text: "Specular Intensity Map",
- value: "specularIntensityMap",
- },
- {
- text: "Transmission",
- value: "transmission",
- },
- {
- text: "Transmission Map",
- value: "transmissionMap",
- },
- {
- text: "Thickness",
- value: "thickness",
- },
- {
- text: "Thickness Map",
- value: "thicknessMap",
- },
- {
- text: "Anisotropy",
- value: "anisotropy",
- },
- {
- text: "Anisotropy Map",
- value: "anisotropyMap",
- },
- {
- text: "Anisotropy Rotation",
- value: "anisotropyRotation",
- },
- {
- text: "Attenuation Distance",
- value: "attenuationDistance",
- },
- {
- text: "Attenuation Color",
- value: "attenuationColor",
- },
- {
- text: "Thickness",
- value: "thickness",
- },
- {
- text: "Iridescence",
- value: "iridescence",
- },
- {
- text: "Iridescence Ior",
- value: "iridescenceIOR",
- },
- {
- text: "Iridescence Map",
- value: "iridescenceMap",
- },
- {
- text: "Iridescence Thickness Range",
- value: "iridescenceThicknessRange",
- },
-
- "|MESH Displacement / Normal / Bump| <-- not a property",
- {
- text: "Displacement Map",
- value: "displacementMap",
- },
- {
- text: "Displacement Scale",
- value: "displacementScale",
- },
- {
- text: "Displacement Bias",
- value: "displacementBias",
- },
- {
- text: "Bump Map",
- value: "bumpMap",
- },
- {
- text: "Bump Scale",
- value: "bumpScale",
- },
- {
- text: "Normal Map Type",
- value: "normalMapType",
- },
-
- "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
- {
- text: "Shininess",
- value: "shininess",
- },
-
- {
- text: "Wireframe",
- value: "wireframe",
- },
- {
- text: "Wireframe Linewidth",
- value: "wireframeLinewidth",
- },
- {
- text: "Wireframe Linecap",
- value: "wireframeLinecap",
- },
- {
- text: "Wireframe Linejoin",
- value: "wireframeLinejoin",
- },
-
- "|POINTS| <-- not a property",
- {
- text: "Size",
- value: "size",
- },
- {
- text: "Size Attenuation",
- value: "sizeAttenuation",
- },
-
- "|LINES| <-- not a property",
- {
- text: "Scale",
- value: "scale",
- },
- {
- text: "Dash Size",
- value: "dashSize",
- },
- {
- text: "Gap Size",
- value: "gapSize",
- },
-
- "|SPRITES| <-- not a property",
- {
- text: "Rotation",
- value: "rotation",
- },
- ],
- },
- blendModes: {
- acceptReporters: false,
- items: [{
- text: "No Blending",
- value: "NoBlending",
- },
- {
- text: "Normal Blending",
- value: "NormalBlending",
- },
- {
- text: "Additive Blending",
- value: "AdditiveBlending",
- },
- {
- text: "Subtractive Blending",
- value: "SubtractiveBlending",
- },
- {
- text: "Multiply Blending",
- value: "MultiplyBlending",
- },
- {
- text: "Custom Blending",
- value: "CustomBlending",
- },
- ],
- },
- depthModes: {
- acceptReporters: false,
- items: [{
- text: "Never Depth",
- value: "NeverDepth",
- },
- {
- text: "Always Depth",
- value: "AlwaysDepth",
- },
- {
- text: "Equal Depth",
- value: "EqualDepth",
- },
- {
- text: "Less Depth",
- value: "LessDepth",
- },
- {
- text: "Less Equal Depth",
- value: "LessEqualDepth",
- },
- {
- text: "Greater Equal Depth",
- value: "GreaterEqualDepth",
- },
- {
- text: "Greater Depth",
- value: "GreaterDepth",
- },
- {
- text: "Not Equal Depth",
- value: "NotEqualDepth",
- },
- ],
- },
- materialTypes: {
- acceptReporters: false,
- items: [{
- text: "Mesh Basic Material",
- value: "MeshBasicMaterial",
- },
- {
- text: "Mesh Standard Material",
- value: "MeshStandardMaterial",
- },
- {
- text: "Mesh Physical Material",
- value: "MeshPhysicalMaterial",
- },
- {
- text: "Mesh Lambert Material",
- value: "MeshLambertMaterial",
- },
- {
- text: "Mesh Phong Material",
- value: "MeshPhongMaterial",
- },
- {
- text: "Mesh Depth Material",
- value: "MeshDepthMaterial",
- },
- {
- text: "Mesh Normal Material",
- value: "MeshNormalMaterial",
- },
- {
- text: "Mesh Matcap Material",
- value: "MeshMatcapMaterial",
- },
- {
- text: "Mesh Toon Material",
- value: "MeshToonMaterial",
- },
- {
- text: "Line Basic Material",
- value: "LineBasicMaterial",
- },
- {
- text: "Line Dashed Material",
- value: "LineDashedMaterial",
- },
- {
- text: "Points Material",
- value: "PointsMaterial",
- },
- {
- text: "Sprite Material",
- value: "SpriteMaterial",
- },
- {
- text: "Shadow Material",
- value: "ShadowMaterial",
- },
- ],
- },
- textureModes: {
- acceptReporters: false,
- items: ["Pixelate", "Blur"],
- },
- textureStyles: {
- acceptReporters: false,
- items: ["Repeat", "Clamp"],
- },
- geometryTypes: {
- acceptReporters: false,
- items: [{
- text: "Box Geometry",
- value: "BoxGeometry",
- },
- {
- text: "Sphere Geometry",
- value: "SphereGeometry",
- },
- {
- text: "Cylinder Geometry",
- value: "CylinderGeometry",
- },
- {
- text: "Plane Geometry",
- value: "PlaneGeometry",
- },
- {
- text: "Circle Geometry",
- value: "CircleGeometry",
- },
- {
- text: "Torus Geometry",
- value: "TorusGeometry",
- },
- {
- text: "Torus Knot Geometry",
- value: "TorusKnotGeometry",
- },
- ],
- },
- modelsList: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".glb"));
- if (models.length < 1) return [
- ["Load a model! (GLB Loader category)"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- fonts: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".json"));
- if (models.length < 1) return [
- ["Load a font!"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- },
- };
- }
-
- addObject(args) {
- const object = new THREE[args.TYPE]();
-
- object.castShadow = true;
- object.receiveShadow = true;
-
- createObject(args.OBJECT3D, object, args.GROUP);
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D);
- const clone = object.clone(true);
- clone.name;
- createObject(args.NAME, clone, args.GROUP);
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D);
- let values = JSON.parse(args.VALUE);
-
- function degToRad(deg) {
- return (deg * Math.PI) / 180;
- }
-
- if (object.rigidBody) {
- const x = values[0];
- const y = values[1];
- const z = values[2];
- if (args.PROPERTY === "rotation") {
- const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
- const quaternion = new THREE.Quaternion();
- quaternion.setFromEuler(euler);
-
- object.rigidBody.setRotation({
- x: quaternion.x,
- y: quaternion.y,
- z: quaternion.z,
- w: quaternion.w,
- });
- } else if (args.PROPERTY === "position") {
- object.rigidBody.setTranslation({
- x: x,
- y: y,
- z: z,
- },
- true
- );
- }
- return;
- }
-
- if (object.isCamera == true && controls) {}
-
- if (args.PROPERTY === "rotation") {
- values = values.map((v) => (v * Math.PI) / 180);
- object.rotation.set(0, 0, 0);
- }
- if (object.isDirectionalLight == true) {
- object.pos = new THREE.Vector3(...values);
- console.log(true, values, object.pos);
- return;
- }
- object[args.PROPERTY].set(...values);
-
- if (object.type == "CubeCamera") object.updateCoordinateSystem();
- }
- /*
- changeObjectV3(args) {
- getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
-
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.x += values[0]
- object.rotation.y += values[1]
- object.rotation.z += values[2]
- }
- else {
- object[args.PROPERTY].add(...values);
- }
- }
- changeObjectXV3(args) {
- getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "rotation") value = value * Math.PI / 180
-
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D);
- if (!object) return;
- let values = vector3ToString(object[args.PROPERTY]);
- if (args.PROPERTY === "rotation") {
- const toDeg = Math.PI / 180;
- values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
- }
-
- return JSON.stringify(values);
- }
- setObject(args) {
- let object = getObject(args.OBJECT3D);
- let value = args.VALUE;
- if (args.PROPERTY === "material") {
- const mat = materials[args.NAME];
- if (mat) value = mat;
- else value = undefined;
- } else if (args.PROPERTY === "geometry") {
- const geo = geometries[args.NAME];
- if (geo) value = geo;
- else value = undefined;
- } else value = !!value;
-
- if (value == undefined) return; //invalid geo/mat
- object[args.PROPERTY] = value;
- }
- getObject(args) {
- let object = getObject(args.OBJECT3D);
- if (!object) return;
- let value;
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value;
- }
- removeObject(args) {
- removeObject(args.OBJECT3D);
- }
- objectE(args) {
- return scene.children.map((o) => o.name).includes(args.NAME);
- }
-
- //defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
-
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
- const mat = materials[args.NAME];
-
- let value = args.VALUE;
-
- if (args.VALUE == "false") value = false;
-
- if (args.PROPERTY == "side") {
- value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
- } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
- else value = getAsset(value);
-
- console.log("o:", args.VALUE, typeof args.VALUE);
- console.log("r:", value, typeof value);
-
- mat[args.PROPERTY] = await value; //await incase its a texture
- mat.needsUpdate = true;
- }
- setBlending(args) {
- const mat = materials[args.NAME];
- mat.blending = THREE[args.VALUE];
- mat.premultipliedAlpha = true;
- mat.needsUpdate = true;
- }
- setDepth(args) {
- const mat = materials[args.NAME];
- mat.depthFunc = THREE[args.VALUE];
- mat.needsUpdate = true;
- }
- removeMaterial(args) {
- const mat = materials[args.NAME];
- mat.dispose();
- delete materials[args.NAME];
- }
- materialE(args) {
- return materials[args.NAME] ? true : false;
- }
-
- newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
- const geo = new THREE[args.TYPE]();
- geo.name = args.NAME;
-
- geometries[args.NAME] = geo;
- }
- setGeometry(args) {
- const geo = geometries[args.NAME];
- geo[args.PROPERTY] = args.VALUE;
-
- geo.needsUpdate = true;
- }
- removeGeometry(args) {
- const geo = geometries[args.NAME];
- geo.dispose();
- delete geometries[args.NAME];
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false;
- }
-
- newGeo(args) {
- const geometry = new THREE.BufferGeometry();
- geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME];
- const positions = args.POINTS.split(" ")
- .map((v) => JSON.parse(v))
- .flat(); //array of v3 of each vertex of each triangle
-
- geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
- geometry.computeVertexNormals();
-
- geometry.needsUpdate = true;
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME];
- const UVs = args.POINTS.split(" ")
- .map((v) => JSON.parse(v))
- .flat(); //array of v2 of each UV of each triangle
-
- geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
- geometry.needsUpdate = true;
- }
-
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
- geometry.name = args.NAME;
-
- geometries[args.NAME] = geometry;
- }
-
- async splineModel(args) {
- const model = await getModel(args.MODEL, args.NAME);
- if (!model) return console.warn("Model not found:", args.MODEL);
-
- const curve = getAsset(args.CURVE);
- const spacing = parseFloat(args.SPACING) || 1;
- const curveLength = curve.getLength();
- const divisions = Math.floor(curveLength / spacing);
-
- const geomList = [];
- const matList = [];
-
- for (let i = 0; i <= divisions; i++) {
- const t = i / divisions;
- const pos = curve.getPointAt(t);
- const tangent = curve.getTangentAt(t);
-
- const temp = model.clone(true);
- temp.position.copy(pos);
-
- const up = new THREE.Vector3(0, 0, 1);
- const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
- temp.quaternion.copy(quat);
-
- temp.updateMatrixWorld(true);
-
- temp.traverse((child) => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone();
- geom.applyMatrix4(child.matrixWorld);
- geomList.push(geom);
- matList.push(child.material); //.clone() ?
- }
- });
- }
-
- const validGeoms = geomList.filter((g) => {
- const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
- if (!ok) console.warn("geometry skipped:", g);
- return ok;
- });
-
- const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
- merged.computeBoundingBox();
- merged.computeBoundingSphere();
-
- merged.name = args.NAME;
- geometries[args.NAME] = merged;
- matList.name = args.NAME;
- materials[args.NAME] = matList;
- }
-
- async text(args) {
- const fontFile = runtime
- .getTargetForStage()
- .getSounds()
- .find((c) => c.name === args.FONT);
- if (!fontFile) return;
-
- const json = new TextDecoder().decode(fontFile.asset.data.buffer);
- const fontData = JSON.parse(json);
-
- const font = fontLoad.parse(fontData);
-
- const params = {
- font: font,
- size: JSON.parse(args.S),
- height: JSON.parse(args.D),
- curveSegments: JSON.parse(args.CS),
- bevelEnabled: false,
- };
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
- geometry.computeVertexNormals();
- geometry.center(); // optional, recenters the text
-
- geometry.name = args.NAME;
-
- geometries[args.NAME] = geometry;
- }
-
- async loadFont() {
- openFileExplorer(".json").then((files) => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- // From lily's assets
- // // Thank you PenguinMod for providing this code.
-
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- const buffer = arrayBuffer;
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Font loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading font.");
- }
-
- // End of PenguinMod
- };
-
- reader.readAsArrayBuffer(file);
- });
- }
- openConv() {
- {
- open("https://gero3.github.io/facetype.js/");
- }
- }
- }
- Scratch.extensions.register(new ThreeObjects());
-
- class ThreeLights {
- getInfo() {
- return {
- id: "threeLights",
- name: "Three Lights",
- color1: "#c7a22aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addLight",
- blockType: Scratch.BlockType.COMMAND,
- text: "add light [NAME] type [TYPE] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myLight",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "lightTypes",
- },
- },
- },
- {
- opcode: "setLight",
- blockType: Scratch.BlockType.COMMAND,
- text: "set light [NAME][PROPERTY] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "lightProperties",
- defaultValue: "intensity",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myLight",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- exemptFromNormalization: true,
- },
- },
- },
- ],
- menus: {
- lightTypes: {
- acceptReporters: false,
- items: [{
- text: "Ambient Light",
- value: "AmbientLight",
- },
- {
- text: "Directional Light",
- value: "DirectionalLight",
- },
- {
- text: "Point Light",
- value: "PointLight",
- },
- {
- text: "Hemisphere Light",
- value: "HemisphereLight",
- },
- {
- text: "Spot Light",
- value: "SpotLight",
- },
- ],
- },
- lightProperties: {
- acceptReporters: false,
- items: [{
- text: "Color",
- value: "color",
- },
- {
- text: "Intensity",
- value: "intensity",
- },
- {
- text: "Cast Shadow?",
- value: "castShadow",
- },
- {
- text: "Ground Color (HemisphereLight)",
- value: "groundColor",
- },
- {
- text: "Map (SpotLight)",
- value: "map",
- },
- {
- text: "Distance (SpotLight)",
- value: "distance",
- },
- {
- text: "Decay (SpotLight)",
- value: "decay",
- },
- {
- text: "Penumbra (SpotLight)",
- value: "penumbra",
- },
- {
- text: "Angle/Size (SpotLight)",
- value: "angle",
- },
- {
- text: "Power (SpotLight)",
- value: "power",
- },
- {
- text: "Target Position (Directional/SpotLight)",
- value: "target",
- },
- ],
- },
- },
- };
- }
-
- addLight(args) {
- const light = new THREE[args.TYPE](0xffffff, 1);
-
- createObject(args.NAME, light, args.GROUP);
- lights[args.NAME] = light;
- if (light.type === "AmbientLight" || "HemisphereLight") return;
-
- light.castShadow = true;
- if (light.type === "PointLight") return;
- //Directional & Spot Light
- light.target.position.set(0, 0, 0);
- scene.add(light.target);
-
- light.pos = new THREE.Vector3(0, 0, 0);
-
- light.shadow.mapSize.width = 4096;
- light.shadow.mapSize.height = 2048;
-
- if (light.type === "SpotLight") {
- light.decay = 0;
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true;
- light.needsUpdate = true;
- }
-
- setLight(args) {
- const light = lights[args.NAME];
- if (!args.PROPERTY) return;
- if (args.PROPERTY === "target") {
- light.target.position.set(...JSON.parse(args.VALUE)); //vector3
- light.target.updateMatrixWorld();
- } else {
- light[args.PROPERTY] = getAsset(args.VALUE);
- }
- light.needsUpdate = true;
-
- if (light.type === "AmbientLight" || "HemisphereLight") return;
-
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true;
- }
- }
- Scratch.extensions.register(new ThreeLights());
-
- class ThreeUtilities {
- getInfo() {
- return {
- id: "threeUtility",
- name: "Three Utilities",
- color1: "#3875c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "newVector2",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Vector [X] [Y]",
- arguments: {
- X: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- },
- },
- },
- {
- opcode: "newVector3",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Vector [X] [Y] [Z]",
- arguments: {
- X: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Z: {
- type: Scratch.ArgumentType.NUMBER,
- },
- },
- },
- "---",
- {
- opcode: "operateV3",
- blockType: Scratch.BlockType.REPORTER,
- text: "do [V3] [O] [V32]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- O: {
- type: Scratch.ArgumentType.STRING,
- menu: "operators",
- },
- V32: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- {
- opcode: "moveVector3",
- blockType: Scratch.BlockType.REPORTER,
- text: "move [S] steps in vector [V3] in direction [D3]",
- arguments: {
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- D3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- {
- opcode: "directionTo",
- blockType: Scratch.BlockType.REPORTER,
- text: "direction from [V3] to [T3]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,3]",
- },
- T3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- "---",
- {
- opcode: "newColor",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Color [HEX]",
- arguments: {
- HEX: {
- type: Scratch.ArgumentType.COLOR,
- defaultValue: "#9966ff",
- },
- },
- },
- {
- opcode: "newFog",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Fog [COLOR] [NEAR] [FAR]",
- arguments: {
- COLOR: {
- type: Scratch.ArgumentType.COLOR,
- defaultValue: "#9966ff",
- exemptFromNormalization: true,
- },
- NEAR: {
- type: Scratch.ArgumentType.NUMBER,
- },
- FAR: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 10,
- },
- },
- },
- {
- opcode: "newTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
- arguments: {
- COSTUME: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- STYLE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureStyles",
- },
- X: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- {
- opcode: "newCubeTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
- arguments: {
- COSTUMEX0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEX1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEY0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEY1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEZ0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEZ1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- STYLE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureStyles",
- },
- X: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- {
- opcode: "newEquirectangularTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Equirectangular Texture [COSTUME] [MODE]",
- arguments: {
- COSTUME: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- },
- },
- "---",
- {
- opcode: "curve",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.REPORTER,
- text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
- arguments: {
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "curveTypes",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
- },
- CLOSED: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "true",
- },
- },
- },
- "---",
- {
- opcode: "mouseDown",
- extensions: ["colours_sensing"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "mouse [BUTTON] [action]?",
- arguments: {
- BUTTON: {
- type: Scratch.ArgumentType.STRING,
- menu: "mouseButtons",
- },
- action: {
- type: Scratch.ArgumentType.STRING,
- menu: "mouseAction",
- },
- },
- },
- {
- opcode: "mousePos",
- extensions: ["colours_sensing"],
- blockType: Scratch.BlockType.REPORTER,
- text: "mouse position",
- arguments: {},
- },
- "---",
- {
- opcode: "getItem",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.REPORTER,
- text: "get item [ITEM] of [ARRAY]",
- arguments: {
- ITEM: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- ARRAY: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: `["myObject", "myLight"]`,
- },
- },
- },
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Raycasting",
- },
- {
- opcode: "raycast",
- blockType: Scratch.BlockType.COMMAND,
- text: "Raycast from [V3] in direction [D3]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,3]",
- },
- D3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,1]",
- },
- },
- },
- {
- opcode: "getRaycast",
- blockType: Scratch.BlockType.REPORTER,
- text: "get raycast [PROPERTY]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "raycastProperties",
- },
- },
- },
- ],
- menus: {
- materialProperties: {
- acceptReporters: false,
- items: [{
- text: "Color",
- value: "color",
- },
- {
- text: "Map (texture)",
- value: "map",
- },
- {
- text: "Alpha Map (texture)",
- value: "alphaMap",
- },
- {
- text: "Alpha Test (0-1)",
- value: "alphaTest",
- },
- {
- text: "Side (front/back/double)",
- value: "side",
- },
- {
- text: "Bump Map (texture)",
- value: "bumpMap",
- },
- {
- text: "Bump Scale",
- value: "bumpScale",
- },
- ],
- },
- textureModes: {
- acceptReporters: false,
- items: ["Pixelate", "Blur"],
- },
- textureStyles: {
- acceptReporters: false,
- items: ["Repeat", "Clamp"],
- },
- raycastProperties: {
- acceptReporters: false,
- items: [{
- text: "Intersected Object Names",
- value: "name",
- },
- {
- text: "Number of Objects",
- value: "number",
- },
- {
- text: "Intersected Objects distances",
- value: "distance",
- },
- ],
- },
- mouseButtons: {
- acceptReporters: false,
- items: ["left", "middle", "right"],
- },
- mouseAction: {
- acceptReporters: false,
- items: ["Down", "Clicked"],
- },
- curveTypes: {
- acceptReporters: false,
- items: ["CatmullRomCurve3"],
- },
- operators: {
- acceptReporters: false,
- items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
- },
- },
- };
- }
- mouseDown(args) {
- if (args.action === "Down") return isMouseDown[args.BUTTON];
- if (args.action === "Clicked") {
- if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
- else prevMouse[args.BUTTON] = true;
- return true;
- }
- }
- mousePos(event) {
- return JSON.stringify(mouseNDC);
- }
- newVector3(args) {
- return JSON.stringify([args.X, args.Y, args.Z]);
- }
- operateV3(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3));
- const v32 = new THREE.Vector3(...JSON.parse(args.V32));
-
- let r;
- if (args.O == "+") r = v3.add(v32);
- else if (args.O == "-") r = v3.sub(v32);
- else if (args.O == "*") r = v3.multiply(v32);
- else if (args.O == "/") r = v3.divide(v32);
- else if (args.O == "=") r = v3.equals(v32);
- else if (args.O == "max") r = v3.max(v32);
- else if (args.O == "min") r = v3.min(v32);
- else if (args.O == "dot") r = v3.dot(v32);
- else if (args.O == "cross") r = v3.cross(v32);
- else if (args.O == "distance to") r = v3.distanceTo(v32);
- else if (args.O == "angle to") r = v3.angleTo(v32);
- else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
-
- if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
- else return JSON.stringify(r);
- }
-
- newVector2(args) {
- return JSON.stringify([args.X, args.Y]);
- }
-
- moveVector3(args) {
- const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
- const steps = Number(args.S);
-
- const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
-
- const yaw = THREE.MathUtils.degToRad(yawInputDeg);
- const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
- const roll = THREE.MathUtils.degToRad(rollInputDeg);
-
- const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
-
- const forwardVector = new THREE.Vector3(0, 0, -1);
- const direction = forwardVector.applyEuler(euler).normalize();
-
- const newPos = currentPos.add(direction.multiplyScalar(steps));
- return JSON.stringify([newPos.x, newPos.y, newPos.z]);
- }
-
- directionTo(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3));
- const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
-
- const direction = toV3.clone().sub(v3).normalize();
- // Pitch (X)
- const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
- // Yaw (Y)
- const yaw = Math.atan2(direction.x, direction.z);
-
- // Roll always 0
- return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
- }
-
- newColor(args) {
- const color = new THREE.Color(args.HEX);
- const uuid = crypto.randomUUID();
- assets.colors[uuid] = color;
- return `colors/${uuid}`;
- }
- newFog(args) {
- const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
- const uuid = crypto.randomUUID();
- assets.fogs[uuid] = fog;
- return `fogs/${uuid}`;
- }
- async newTexture(args) {
- const textureURI = encodeCostume(args.COSTUME);
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
- async newCubeTexture(args) {
- const uris = [
- encodeCostume(args.COSTUMEX0),
- encodeCostume(args.COSTUMEX1),
- encodeCostume(args.COSTUMEY0),
- encodeCostume(args.COSTUMEY1),
- encodeCostume(args.COSTUMEZ0),
- encodeCostume(args.COSTUMEZ1),
- ];
- const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME);
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME;
- texture.mapping = THREE.EquirectangularReflectionMapping;
-
- setTexutre(texture, args.MODE);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
-
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g);
- if (!matches) return [];
-
- return matches.map((str) => {
- const nums = str
- .replace(/[\[\]\s]/g, "")
- .split(",")
- .map(Number);
- return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
- });
- }
- const points = parsePoints(args.POINTS);
- const curve = new THREE[args.TYPE](points);
- curve.closed = JSON.parse(args.CLOSED);
-
- const uuid = crypto.randomUUID();
- assets.curves[uuid] = curve;
- return `curves/${uuid}`;
- }
-
- getItem(args) {
- const items = JSON.parse(args.ARRAY);
- const item = items[args.ITEM - 1];
- if (!item) return "0";
- return item;
- }
-
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3));
- // rotation is in degrees => convert to radians first
- const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
-
- const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
- const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
-
- const raycaster = new THREE.Raycaster();
- //const camera = getObject(args.CAMERA)
- raycaster.set(origin, direction);
-
- const intersects = raycaster.intersectObjects(scene.children, true);
-
- raycastResult = intersects;
- }
- getRaycast(args) {
- if (args.PROPERTY === "number") return raycastResult.length;
- if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
- return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
- }
- }
- Scratch.extensions.register(new ThreeUtilities());
-
- class ThreeGLB {
- getInfo() {
- return {
- id: "threeGLB",
- name: "Three GLB Loader",
- color1: "#c53838ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- blockType: Scratch.BlockType.BUTTON,
- text: "Load GLB File",
- func: "loadModelFile",
- },
- {
- opcode: "addModel",
- blockType: Scratch.BlockType.COMMAND,
- text: "add [ITEM] as [NAME] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- ITEM: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelsList",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- },
- },
- {
- opcode: "getModel",
- blockType: Scratch.BlockType.REPORTER,
- text: "get object [PROPERTY] of [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelProperties",
- },
- },
- },
- {
- opcode: "playAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "play animation [ANAME] of [NAME], [TIMES] times",
- arguments: {
- TIMES: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "0",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "pauseAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [TOGGLE] animation [ANAME] of [NAME]",
- arguments: {
- TOGGLE: {
- type: Scratch.ArgumentType.NUMBER,
- menu: "pauseUn",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "stopAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "stop animation [ANAME] of [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- ],
- menus: {
- modelProperties: {
- acceptReporters: false,
- items: [{
- text: "Animations",
- value: "animations",
- }, ],
- },
- pauseUn: {
- acceptReporters: true,
- items: [{
- text: "Pause",
- value: "true",
- },
- {
- text: "Unpasue",
- value: "false",
- },
- ],
- },
- modelsList: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".glb"));
- if (models.length < 1) return [
- ["Load a model!"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- },
- };
- }
-
- async loadModelFile() {
- openFileExplorer(".glb").then((files) => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- {
- // From lily's assets
-
- // Thank you PenguinMod for providing this code.
- {
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- //const res = await Scratch.fetch(args.URL);
- //const buffer = await res.arrayBuffer();
- const buffer = arrayBuffer;
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Model loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading model.");
- }
- }
- // End of PenguinMod
- }
- };
-
- reader.readAsArrayBuffer(file);
- });
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME);
-
- createObject(args.NAME, group, args.GROUP);
- }
- getModel(args) {
- if (!models[args.NAME]) return;
- return Object.keys(models[args.NAME].actions).toString();
- }
-
- playAnimation(args) {
- const model = models[args.NAME];
- if (!model) {
- console.log("no model!");
- return;
- }
-
- const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
- if (!action) {
- console.log("no action!");
- return;
- }
-
- args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
-
- action.reset().play();
- }
- stopAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.stop();
- }
- pauseAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.paused = args.TOGGLE;
- }
- }
- Scratch.extensions.register(new ThreeGLB());
-
- class ThreeAddons {
- getInfo() {
- return {
- id: "threeAddons",
- name: "Three Addons",
- color1: "#c538a2ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- blockType: Scratch.BlockType.LABEL,
- text: "Orbit Control",
- },
- {
- opcode: "OrbitControl",
- blockType: Scratch.BlockType.COMMAND,
- text: "set addon Orbit Control [STATE]",
- arguments: {
- STATE: {
- type: Scratch.ArgumentType.STRING,
- menu: "onoff",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "Post Processing",
- },
- {
- opcode: "resetComposer",
- blockType: Scratch.BlockType.COMMAND,
- text: "reset composer",
- },
- {
- opcode: "bloom",
- blockType: Scratch.BlockType.COMMAND,
- text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- I: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.5,
- },
- T: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.5,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- },
- },
- {
- opcode: "godRays",
- blockType: Scratch.BlockType.COMMAND,
- text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- DEC: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.95,
- },
- DENS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- EXP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.1,
- },
- WEI: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.4,
- },
- RES: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- SAMP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 64,
- },
- },
- },
- {
- opcode: "dots",
- blockType: Scratch.BlockType.COMMAND,
- text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- A: {
- type: Scratch.ArgumentType.ANGLE,
- defaultValue: 0,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- },
- },
- {
- opcode: "depth",
- blockType: Scratch.BlockType.COMMAND,
- text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
- arguments: {
- FD: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 3,
- },
- FL: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.001,
- },
- BS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 4,
- },
- H: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 240,
- },
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "NORMAL",
- },
- },
- },
- "---",
- {
- opcode: "custom",
- blockType: Scratch.BlockType.COMMAND,
- text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myShader",
- },
- FRA: {
- type: Scratch.ArgumentType.STRING,
- },
- VER: {
- type: Scratch.ArgumentType.STRING,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "NORMAL",
- },
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- ],
- menus: {
- onoff: {
- acceptReporters: true,
- items: [{
- text: "enabled",
- value: "1",
- },
- {
- text: "disabled",
- value: "0",
- },
- ],
- },
- blendModes: {
- acceptReporters: false,
- items: [
- "SKIP",
- "SET",
- "ADD",
- "ALPHA",
- "AVERAGE",
- "COLOR",
- "COLOR_BURN",
- "COLOR_DODGE",
- "DARKEN",
- "DIFFERENCE",
- "DIVIDE",
- "DST",
- "EXCLUSION",
- "HARD_LIGHT",
- "HARD_MIX",
- "HUE",
- "INVERT",
- "INVERT_RGB",
- "LIGHTEN",
- "LINEAR_BURN",
- "LINEAR_DODGE",
- "LINEAR_LIGHT",
- "LUMINOSITY",
- "MULTIPLY",
- "NEGATION",
- "NORMAL",
- "OVERLAY",
- "PIN_LIGHT",
- "REFLECT",
- "SCREEN",
- "SRC",
- "SATURATION",
- "SOFT_LIGHT",
- "SUBTRACT",
- "VIVID_LIGHT",
- ],
- },
- },
- };
- }
-
- OrbitControl(args) {
- if (controls) controls.dispose();
-
- console.log("creating...", OrbitControls);
- controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
- controls.enableDamping = true;
-
- controls.enabled = !!args.STATE;
- console.log(controls);
- }
-
- resetComposer() {
- composer.passes = [];
- passes = {};
- customEffects = [];
- updateComposers();
- }
-
- bloom(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const bloomEffect = new BloomEffect({
- intensity: args.I,
- luminanceThreshold: args.T, // ← correct key
- luminanceSmoothing: args.S,
- blendFunction: BlendFunction[args.BLEND],
- });
- bloomEffect.blendMode.opacity.value = args.OP;
-
- const pass = new EffectPass(camera, bloomEffect);
-
- composer.addPass(pass);
- }
-
- godRays(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- let object = getObject(args.NAME);
- const sun = object;
-
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- });
- godRays.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, godRays);
- composer.addPass(pass);
- }
-
- dots(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const dot = new DotScreenEffect({
- angle: args.A,
- scale: args.S,
- blendFunction: BlendFunction[args.BLEND],
- });
- dot.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, dot);
- composer.addPass(pass);
- }
-
- depth(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- });
- dofEffect.blendMode.opacity.value = args.OP;
-
- const dofPass = new EffectPass(camera, dofEffect);
- composer.addPass(dofPass);
- }
-
- async custom(args) {
- function cleanGLSL(glslCode) {
- //delete multilines comments
- let cleanedCode = glslCode
- .replace(/\/\*[\s\S]*?\*\//g, " ")
- .replace(/ /g, "\n")
- .replace(/\/\/.*$/gm, " ")
- .replace(/; /g, ";\n");
-
- return cleanedCode;
- }
-
- let fs = cleanGLSL(`
- ${args.FRA}
- `);
- if (!args.FRA.trim()) {
- fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
- }
- const vs = cleanGLSL(`
- ${args.VER}
- `);
- console.log(fs);
- console.log(vs);
-
- const effect = new Effect("Custom", fs, {
- blendFunction: BlendFunction[args.BLEND],
- vertexShader: vs,
- uniforms: new Map([
- //uniforms usually in shaders... open to more!
- ["time", new THREE.Uniform(0.0)],
- [
- "resolution",
- new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
- ],
- ]),
- defines: new Map([
- ["USE_TIME", "1"],
- ["USE_VERTEX_TRANSFORM", ""],
- ]),
- });
-
- effect.blendMode.opacity.value = args.OP;
-
- const pass = new EffectPass(camera, effect);
- composer.addPass(pass);
-
- customEffects.push(effect);
- }
- }
- Scratch.extensions.register(new ThreeAddons());
-
- class RapierPhysics {
- getInfo() {
- return {
- id: "rapierPhysics",
- name: "RAPIER Physics",
- color1: "#222222",
- color2: "#203024ff",
- color3: "#78f07eff",
- blocks: [{
- opcode: "createWorld",
- blockType: Scratch.BlockType.COMMAND,
- text: "create world | gravity:[G]",
- arguments: {
- G: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,-9.81,0]",
- },
- },
- },
- {
- opcode: "getWorld",
- blockType: Scratch.BlockType.REPORTER,
- text: "get world [PROPERTY]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "wProp",
- },
- },
- },
- "---",
- {
- opcode: "objectPhysics",
- blockType: Scratch.BlockType.COMMAND,
- text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
- arguments: {
- state2: {
- type: Scratch.ArgumentType.STRING,
- menu: "state2",
- },
- state: {
- type: Scratch.ArgumentType.STRING,
- menu: "state",
- defaultValue: "true",
- },
- type: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectTypes",
- defaultValue: "dynamic",
- },
- collider: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderTypes",
- defaultValue: "cuboid",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- mass: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- density: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- friction: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "0.5",
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.LABEL,
- text: "- RigidBody",
- },
- {
- opcode: "setRB",
- blockType: Scratch.BlockType.COMMAND,
- text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "rigidBodySets",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "getRB",
- blockType: Scratch.BlockType.REPORTER,
- text: "get rigidbody [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "rigidBodyProperties",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "lockObjectAxis",
- blockType: Scratch.BlockType.COMMAND,
- text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
- arguments: {
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "lockAxes",
- },
- X: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- Y: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- Z: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- },
- },
- "---",
- {
- opcode: "addForce",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,10,0]",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "forces",
- defaultValue: "addForce",
- },
- SPACE: {
- type: Scratch.ArgumentType.STRING,
- menu: "spaces",
- defaultValue: "world",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "resetForces",
- blockType: Scratch.BlockType.COMMAND,
- text: "reset [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "resetF",
- defaultValue: "resetForces",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "enableCCD",
- blockType: Scratch.BlockType.COMMAND,
- text: "enable Continuous Collision Detection for [OBJECT] [state]",
- arguments: {
- state: {
- type: Scratch.ArgumentType.STRING,
- menu: "state",
- defaultValue: "true",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "oPropS",
- defaultValue: "physics",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "fixedJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- RA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- RB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- {
- opcode: "sphericalJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- },
- },
- {
- opcode: "revoluteJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- X: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.LABEL,
- text: "- Collider",
- },
- {
- opcode: "setC",
- blockType: Scratch.BlockType.COMMAND,
- text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderSets",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "getC",
- blockType: Scratch.BlockType.REPORTER,
- text: "get collider [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderProperties",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "sensorSingle",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is sensor [SENSOR] touching [OBJECT]?",
- arguments: {
- SENSOR: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySensor",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "sensorAll",
- blockType: Scratch.BlockType.REPORTER,
- text: "objects touching sensor [SENSOR]",
- arguments: {
- SENSOR: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySensor",
- },
- },
- },
- ],
- menus: {
- wProp: {
- acceptReporters: false,
- items: [{
- text: "Gravity",
- value: "gravity",
- },
- {
- text: "log to console",
- value: "log",
- },
- ],
- },
- tf: {
- acceptReporters: true,
- items: [{
- text: "false",
- value: "false",
- },
- {
- text: "true",
- value: "true",
- },
- ],
- },
- lockAxes: {
- acceptReporters: false,
- items: [{
- text: "Translation",
- value: "setEnabledTranslations",
- },
- {
- text: "Rotation",
- value: "setEnabledRotations",
- },
- ],
- },
- rigidBodyProperties: {
- acceptReporters: false,
- items: [{
- text: "Type",
- value: "bodyType",
- },
- {
- text: "Linear Velocity",
- value: "linvel",
- },
- {
- text: "Angular Velocity",
- value: "angvel",
- },
- {
- text: "Translation (position)",
- value: "translation",
- },
- {
- text: "Rotation (quaternion)",
- value: "rotation",
- },
- {
- text: "Mass",
- value: "mass",
- },
- //{text: "Center of Mass", value: "centerOfMass"},
- {
- text: "Linear Damping",
- value: "linearDamping",
- },
- {
- text: "Angular Damping",
- value: "angularDamping",
- },
- {
- text: "Is Sleeping?",
- value: "isSleeping",
- },
- //{text: "Can Sleep?", value: "isCanSleep"},
- {
- text: "Gravity Scale",
- value: "gravityScale",
- },
- {
- text: "Is Fixed?",
- value: "isFixed",
- },
- {
- text: "Is Dynamic?",
- value: "isDynamic",
- },
- {
- text: "Is Kinematic?",
- value: "isKinematic",
- },
- //{text: "Sleeping", value: "sleeping"}
- ],
- },
- rigidBodySets: {
- acceptReporters: false,
- items: [
- //{text: "Linear Velocity", value: "setLinvel"},
- //{text: "Angular Velocity", value: "setAngvel"},
- //{text: "Mass", value: "setMass"},
- {
- text: "Gravity Scale",
- value: "setGravityScale",
- },
- //{text: "Can Sleep?", value: "setCanSleep"},
- //{text: "Sleeping", value: "sleeping"},
- {
- text: "Linear Damping",
- value: "setLinearDamping",
- },
- {
- text: "Angular Damping",
- value: "setAngularDamping",
- },
- {
- text: "Is Fixed?",
- value: "isFixed",
- },
- {
- text: "Is Dynamic?",
- value: "isDynamic",
- },
- {
- text: "Is Kinematic?",
- value: "isKinematic",
- },
- ],
- },
- colliderProperties: {
- acceptReporters: false,
- items: [
- //{text: "Collider Type", value: "type"},
- {
- text: "Is Sensor?",
- value: "isSensor",
- },
- {
- text: "Friction",
- value: "friction",
- },
- {
- text: "Restitution",
- value: "restitution",
- },
- {
- text: "Density",
- value: "density",
- },
- {
- text: "Mass",
- value: "mass",
- },
- {
- text: "Position",
- value: "translation",
- },
- {
- text: "Rotation",
- value: "rotation",
- },
- //{text: "Area", value: "area"},
- {
- text: "Volume",
- value: "volume",
- },
- {
- text: "Collision Groups",
- value: "collisionGroups",
- },
- //{text: "Collision Mask", value: "collisionMask"},
- //{text: "Is Enabled?", value: "enabled"},
- //{text: "Contact Count", value: "contactCount"},
- //{text: "RigidBody Handle", value: "rigidBody"}
- ],
- },
- colliderSets: {
- acceptReporters: false,
- items: [{
- text: "Friction",
- value: "setFriction",
- },
- {
- text: "Restitution",
- value: "setRestitution",
- },
- {
- text: "Density",
- value: "setDensity",
- },
- {
- text: "Is Sensor?",
- value: "setSensor",
- },
- {
- text: "Collision Groups",
- value: "setCollisionGroups",
- },
- //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
- //{text: "Position Offset", value: "setTranslation"},
- //{text: "Rotation Offset", value: "setRotation"}
- ],
- },
- state: {
- acceptReporters: true,
- items: [{
- text: "on",
- value: "true",
- },
- {
- text: "off",
- value: "false",
- },
- ],
- },
- state2: {
- acceptReporters: true,
- items: [{
- text: "false",
- value: "false",
- },
- {
- text: "true (must be fixed)",
- value: "true",
- },
- ],
- },
- spaces: {
- acceptReporters: false,
- items: [{
- text: "World",
- value: "world",
- },
- {
- text: "Local",
- value: "local",
- },
- ],
- },
- objectTypes: {
- acceptReporters: false,
- items: [{
- text: "Dynamic",
- value: "dynamic",
- },
- {
- text: "Fixed",
- value: "fixed",
- },
- {
- text: "Kinematic Position Based",
- value: "kinematicPositionBased",
- },
- ],
- },
- colliderTypes: {
- acceptReporters: false,
- items: [{
- text: "Box, Rectangle, cuboid",
- value: "cuboid",
- },
- {
- text: "Sphere, ball",
- value: "ball",
- },
- {
- text: "Custom, complex simple shapes, convexHull",
- value: "convexHull",
- },
- {
- text: "Precision, TriMesh",
- value: "trimesh",
- },
- ],
- },
- forces: {
- acceptReporters: false,
- items: [{
- text: "Force",
- value: "addForce",
- },
- {
- text: "Torque (rotation)",
- value: "addTorque",
- },
- {
- text: "Apply Impulse",
- value: "applyImpulse",
- },
- {
- text: "Apply Torque Impulse (rotation)",
- value: "applyTorqueImpulse",
- },
- {
- text: "Linear Velocity",
- value: "setLinvel",
- },
- {
- text: "Angular Velocity",
- value: "setAngvel",
- },
- ],
- },
- resetF: {
- acceptReporters: false,
- items: [{
- text: "Forces",
- value: "resetForces",
- },
- {
- text: "Torques",
- value: "resetTorques",
- },
- ],
- },
- },
- };
- }
- joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
- }
-
- fixedJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- let RA = JSON.parse(args.RA).map(Number);
- let RB = JSON.parse(args.RB).map(Number);
-
- RA = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RA[0]),
- THREE.MathUtils.degToRad(RA[1]),
- THREE.MathUtils.degToRad(RA[2])
- )
- );
- RB = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RB[0]),
- THREE.MathUtils.degToRad(RB[1]),
- THREE.MathUtils.degToRad(RB[2])
- )
- );
-
- const data = RAPIER.JointData.fixed({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- },
- RA, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- },
- RB
- );
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- sphericalJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
-
- const data = RAPIER.JointData.spherical({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- revoluteJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- const x = JSON.parse(args.X).map(Number);
-
- const data = RAPIER.JointData.revolute({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- }, {
- x: x[0],
- y: x[1],
- z: x[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- prismaticJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- const x = JSON.parse(args.X).map(Number);
-
- const data = RAPIER.JointData.prismatic({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- }, {
- x: x[0],
- y: x[1],
- z: x[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- createWorld(args) {
- const v3 = JSON.parse(args.G).map(Number);
- const gravity = {
- x: v3[0],
- y: v3[1],
- z: v3[2],
- };
- physicsWorld = new RAPIER.World(gravity);
-
- console.log(physicsWorld);
- }
-
- getWorld(args) {
- if (args.PROPERTY === "log") {
- console.log(physicsWorld);
- return "logged";
- }
- return JSON.stringify(physicsWorld[args.PROPERTY]);
- }
-
- setRB(args) {
- let value = args.VALUE;
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
- let object = getObject(args.OBJECT);
- object.rigidBody[args.PROPERTY](value);
- }
- setC(args) {
- let value = args.VALUE;
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
- let object = getObject(args.OBJECT);
- object.collider[args.PROPERTY](value);
- }
-
- getRB(args) {
- let object = getObject(args.OBJECT);
- return JSON.stringify(object.rigidBody[args.PROPERTY]());
- }
- getC(args) {
- let object = getObject(args.OBJECT);
- return JSON.stringify(object.collider[args.PROPERTY]());
- }
-
- lockObjectAxis(args) {
- let object = getObject(args.OBJECT);
- const x = !JSON.parse(args.X);
- const y = !JSON.parse(args.Y);
- const z = !JSON.parse(args.Z);
- object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
- }
-
- objectPhysics(args) {
- let object = getObject(args.OBJECT);
- object.physics = JSON.parse(args.state);
-
- if (JSON.parse(args.state)) {
- //if already exists delete:
- if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody);
- object.rigidBody = null;
- object.collider = null;
- }
- /*asing a rigidbody and collider to object and add them to physicsWorld*/
- let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
- .setTranslation(object.position.x, object.position.y, object.position.z)
- .setRotation({
- w: object.quaternion._w,
- x: object.quaternion._x,
- y: object.quaternion._y,
- z: object.quaternion._z,
- });
-
- let colliderDesc;
- switch (args.collider) {
- case "cuboid":
- colliderDesc = createCuboidCollider(object);
- break;
- case "ball":
- colliderDesc = createBallCollider(object);
- break;
- case "convexHull":
- colliderDesc = createConvexHullCollider(object);
- break;
- case "trimesh":
- colliderDesc = TriMesh(object);
- break;
- }
- colliderDesc
- .setSensor(JSON.parse(args.state2))
- .setMass(args.mass)
- .setDensity(args.density)
- .setFriction(args.friction);
-
- let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
- let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
-
- object.rigidBody = rigidBody;
- object.collider = collider;
- } else {
- /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody);
- object.rigidBody = null;
- object.collider = null;
- }
- }
-
- enableCCD(args) {
- let object = getObject(args.OBJECT);
- if (object.physics) {
- let rigidBody = object.rigidBody;
- rigidBody.enableCcd(JSON.parse(args.state));
- }
- }
-
- addForce(args) {
- let object = getObject(args.OBJECT);
- const vector = JSON.parse(args.VALUE).map(Number);
-
- let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
- if (args.SPACE === "local") {
- force.applyQuaternion(object.quaternion);
- }
-
- object.rigidBody[args.PROPERTY](force, true);
- }
-
- resetForces(args) {
- rigidBody[args.PROPERTY](true);
- }
-
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR);
-
- let object = getObject(args.OBJECT);
-
- let touching = false;
- physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
- if (otherCollider === object.collider) touching = true;
- });
-
- return touching;
- }
-
- sensorAll(args) {
- const sensor = getObject(args.SENSOR);
-
- const touchedObjects = [];
-
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
- // find owner of collider
- const otherObject = scene.children.find((o) => o.collider === otherCollider);
- console.log(otherCollider);
- if (otherObject) touchedObjects.push(otherObject.name);
- });
-
- return JSON.stringify(touchedObjects);
- }
- }
- Scratch.extensions.register(new RapierPhysics());
-
- //Thanks to the PointerLock extension of Turbowarp
- const mouse = vm.runtime.ioDevices.mouse;
- let isLocked = false;
- let isPointerLockEnabled = false;
-
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
-
- const postMouseData = (e, isDown) => {
- const {
- movementX,
- movementY
- } = e;
- const {
- width,
- height
- } = rect;
- const x = mouse._clientX + movementX;
- const y = mouse._clientY - movementY;
- mouse._clientX = x;
- mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
- mouse._clientY = y;
- mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
- if (typeof isDown === "boolean") {
- const data = {
- button: e.button,
- isDown,
- };
- originalPostIOData(data);
- }
- };
-
- const mouseDevice = vm.runtime.ioDevices.mouse;
- const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
- mouseDevice.postData = (data) => {
- if (!isPointerLockEnabled) {
- return originalPostIOData(data);
- }
- };
-
- document.addEventListener(
- "mousedown",
- (e) => {
- // @ts-expect-error
- if (threeRenderer.domElement.contains(e.target)) {
- if (isLocked) {
- postMouseData(e, true);
- } else if (isPointerLockEnabled) {
- threeRenderer.domElement.requestPointerLock();
- }
- }
- },
- true
- );
- document.addEventListener(
- "mouseup",
- (e) => {
- if (isLocked) {
- postMouseData(e, false);
- // @ts-expect-error
- } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
- threeRenderer.domElement.requestPointerLock();
- }
- },
- true
- );
- document.addEventListener(
- "mousemove",
- (e) => {
- if (isLocked) {
- postMouseData(e);
- }
- },
- true
- );
-
- document.addEventListener("pointerlockchange", () => {
- isLocked = document.pointerLockElement === threeRenderer.domElement;
- });
- document.addEventListener("pointerlockerror", (e) => {
- console.error("Pointer lock error", e);
- });
-
- const oldStep = vm.runtime._step;
- vm.runtime._step = function(...args) {
- const ret = oldStep.call(this, ...args);
- if (isPointerLockEnabled) {
- const {
- width,
- height
- } = rect;
- mouse._clientX = width / 2;
- mouse._clientY = height / 2;
- mouse._scratchX = 0;
- mouse._scratchY = 0;
- }
- return ret;
- };
-
- vm.runtime.on("PROJECT_LOADED", () => {
- isPointerLockEnabled = false;
- if (isLocked) {
- document.exitPointerLock();
- }
- });
-
- class Pointerlock {
- getInfo() {
- return {
- id: "threepointerlockmod",
- name: "Pointerlock for Extra 3D",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "setLocked",
- blockType: Scratch.BlockType.COMMAND,
- text: "set pointer lock [enabled]",
- arguments: {
- enabled: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "true",
- menu: "enabled",
- },
- },
- },
- {
- opcode: "isLocked",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "pointer locked?",
- },
- ],
- menus: {
- enabled: {
- acceptReporters: true,
- items: [{
- text: "enabled",
- value: "true",
- },
- {
- text: "disabled",
- value: "false",
- },
- ],
- },
- },
- };
- }
-
- setLocked(args) {
- isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
- if (!isPointerLockEnabled && isLocked) {
- document.exitPointerLock();
- }
- }
-
- isLocked() {
- return isLocked;
- }
- }
- Scratch.extensions.register(new Pointerlock());
- });
-})(Scratch);
diff --git a/threejsD_BACKUP_13852.js b/threejsD_BACKUP_13852.js
deleted file mode 100644
index d1db442..0000000
--- a/threejsD_BACKUP_13852.js
+++ /dev/null
@@ -1,5029 +0,0 @@
-/* jshint esversion: 11 */
-// Name: Extra 3D
-// ID: threejsExtension
-// Description: Use three js inside Turbowarp! A 3D graphics library.
-// By: Civero
-// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
-
-(function(Scratch) {
- "use strict";
-
- if (!Scratch.extensions.unsandboxed) {
- throw new Error("Three-D extension must run unsandboxed");
- }
-
- if (Scratch.vm.runtime.isPackaged) {
- alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
- return;
- }
- //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
-
- const vm = Scratch.vm;
- const runtime = vm.runtime;
- const renderer = Scratch.renderer;
- const canvas = renderer.canvas;
- const Cast = Scratch.Cast;
- const menuIconURI =
- "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
-
- let alerts = false;
- console.log("alerts are " + (alerts ? "enabled" : "disabled"));
-
- let isMouseDown = {
- left: false,
- middle: false,
- right: false,
- };
- let prevMouse = {
- left: false,
- middle: false,
- right: false,
- };
-
- let lastWidth = 0;
- let lastHeight = 0;
-
- let THREE;
- let clock;
- let running;
- let loopId;
- //Addons
- let GLTFLoader;
- let gltf;
- let OrbitControls;
- let controls;
- let BufferGeometryUtils;
- let TextGeometry;
- let fontLoad;
- //Physics
- let RAPIER;
- let physicsWorld;
-
- let threeRenderer;
- let scene;
- let camera;
- let eulerOrder = "YXZ";
-
- let composer;
- let passes = {};
- let customEffects = [];
- let renderTargets = {};
-
- let materials = {};
- let geometries = {};
- let lights = {};
- let models = {};
-
- let assets = {
- //should i place materials, geometries; inside too?
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {}, //not the same as the global one! this one only stores textures
- };
-
- let raycastResult = [];
-
- function resetor(level) {
- camera = undefined;
- composer.reset();
-
- passes = {};
- customEffects = [];
- renderTargets = {};
-
- materials = {};
- geometries = {};
- lights = {};
- models = {};
-
- if (level > 0) {
- assets = {
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {},
- };
- }
-
- updateComposers();
- }
-
- //utility
- function vector3ToString(prop) {
- if (!prop) return "0,0,0";
-
- const x =
- typeof prop.x === "number" ?
- prop.x :
- typeof prop._x === "number" ?
- prop._x :
- JSON.stringify(prop).includes("X") ?
- prop :
- 0;
- const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
- const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
-
- return [x, y, z];
- }
-
- //objects
- function createObject(name, content, parentName) {
- let object = getObject(name, true);
- if (object) {
- removeObject(name);
- alerts ? alert(name + " already exsisted, will replace!") : null;
- }
- content.name = name;
- content.rotation._order = eulerOrder;
- parentName === scene.name ? (object = scene) : (object = getObject(parentName));
- content.physics = false;
-
- object.add(content);
- }
-
- function removeObject(name) {
- let object = getObject(name);
- if (!object) return;
-
- scene.remove(object);
-
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true);
- physicsWorld.removeRigidBody(object.rigidBody, true);
- object.rigidBody = null;
- object.collider = null;
- }
- if (object.isLight) {
- delete lights[name];
- }
- }
-
- function getObject(name, isNew) {
- let object = null;
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
- return;
- }
- object = scene.getObjectByName(name);
- if (!object && !isNew) {
- alerts ? alert(name + " does not exist! Add it to scene") : null;
- return;
- }
- return object;
- }
-
- //materials
- function encodeCostume(name) {
- if (name.startsWith("data:image/")) return name;
- return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
- }
-
- function setTexutre(texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace;
-
- if (mode === "Pixelate") {
- texture.minFilter = THREE.NearestFilter;
- texture.magFilter = THREE.NearestFilter;
- } else {
- //Blur
- texture.minFilter = THREE.NearestMipmapLinearFilter;
- texture.magFilter = THREE.NearestMipmapLinearFilter;
- }
-
- if (style === "Repeat") {
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- texture.repeat.set(x, y);
- }
-
- texture.generateMipmaps = true;
- }
- async function resizeImageToSquare(uri, size = 256) {
- return new Promise((resolve) => {
- const img = new Image();
- img.onload = () => {
- const canvas = document.createElement("canvas");
- canvas.width = size;
- canvas.height = size;
- const ctx = canvas.getContext("2d");
-
- // clear + draw image scaled to fit canvas
- ctx.clearRect(0, 0, size, size);
- ctx.drawImage(img, 0, 0, size, size);
-
- resolve(canvas.toDataURL()); // return normalized Data URI
- //delete canvas?
- };
- img.src = uri;
- });
- }
- //light
- function updateShadowFrustum(light, focusPos) {
- if (light.type !== "DirectionalLight") return;
-
- // Frustum Size - Increase this value to cover a larger area.
- const d = 50;
-
- // Update Orthographic Shadow Camera Frustum
- const shadowCamera = light.shadow.camera;
-
- // Set the width/height of the frustum
- shadowCamera.left = -d;
- shadowCamera.right = d;
- shadowCamera.top = d;
- shadowCamera.bottom = -d;
-
- // Determine ranges
- shadowCamera.near = 0.1;
- shadowCamera.far = 500;
-
- // Position the Light and its Target
- light.target.position.copy(focusPos);
- const direction = light.position.clone().sub(light.target.position).normalize();
- light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
-
- // Ensure matrices are updated.
- light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true;
- }
- //composer
- function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
-
- // always recreate the RenderPass to point to the current scene/camera
- passes["Render"] = new RenderPass(scene, camera);
-
- // ensure composer has a RenderPass as the first pass
- const hasRender = composer.passes.some((p) => p && p.scene);
- if (!hasRender) composer.addPass(passes["Render"]);
- else {
- // if composer already has one, replace it so it references current scene/camera
- const idx = composer.passes.findIndex((p) => p && p.scene);
- composer.passes[idx] = passes["Render"];
- }
- }
- //utility
- function getMouseNDC(event) {
- // Use threeRenderer.domElement for correct offset
- const rect = threeRenderer.domElement.getBoundingClientRect();
- const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
- const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
- return [x, y];
- }
-
- function checkCanvasSize() {
- const {
- width,
- height
- } = canvas;
- if (width !== lastWidth || height !== lastHeight) {
- lastWidth = width;
- lastHeight = height;
- resize();
- }
- requestAnimationFrame(checkCanvasSize); //rerun next frame
- }
- //physics
- function computeWorldBoundingBox(mesh) {
- // Create a Box3 in world coordinates
- const box = new THREE.Box3().setFromObject(mesh);
- const size = new THREE.Vector3();
- box.getSize(size);
- const center = new THREE.Vector3();
- box.getCenter(center);
- return {
- size,
- center,
- };
- }
-
- function createCuboidCollider(mesh) {
- const {
- size
- } = computeWorldBoundingBox(mesh);
- const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
- return collider;
- }
-
- function createBallCollider(mesh) {
- const {
- size
- } = computeWorldBoundingBox(mesh);
- // radius = 1/2 of the largest verticie
- const radius = Math.max(size.x, size.y, size.z) / 2;
- const collider = RAPIER.ColliderDesc.ball(radius);
- return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
- }
-
- function createConvexHullCollider(mesh) {
- mesh.updateWorldMatrix(true, false);
-
- const position = mesh.geometry.attributes.position;
- const vertices = [];
- const vertex = new THREE.Vector3();
-
- // Matrix for scale only
- const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
-
- for (let i = 0; i < position.count; i++) {
- vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
- vertices.push(vertex.x, vertex.y, vertex.z);
- }
-
- const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
- return collider;
- }
-
- function TriMesh(mesh) {
- // Get the positions array (from your geoPoints function)
- const positions = mesh.geometry.attributes.position.array;
- const numVertices = positions.length / 3;
-
- // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
- const indices = Array.from({
- length: numVertices,
- },
- (_, i) => i
- );
-
- const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
-
- return collider;
- }
-
- function getModel(model, name) {
- const file = runtime
- .getTargetForStage()
- .getSounds()
- .find((c) => c.name === model);
- if (!file) return;
-
- return new Promise((resolve, reject) => {
- gltf.parse(
- file.asset.data.buffer,
- "",
- (gltf) => {
- const root = gltf.scene;
- root.traverse((child) => {
- if (child.isMesh) {
- child.castShadow = true;
- child.receiveShadow = true;
- }
- });
-
- const mixer = new THREE.AnimationMixer(root);
- const actions = {};
- gltf.animations.forEach((clip) => {
- const act = mixer.clipAction(clip);
- act.clampWhenFinished = true;
- actions[clip.name] = act;
- });
-
- models[name] = {
- root,
- mixer,
- actions,
- };
- resolve(root);
- },
- (error) => {
- console.error("Error parsing GLB model:", error);
- reject(error);
- }
- );
- });
- }
- async function openFileExplorer(format) {
- return new Promise((resolve) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = format;
- input.multiple = false;
- input.onchange = () => {
- resolve(input.files);
- input.remove();
- };
- input.click();
- });
- }
-
- function getMeshesUsingTexture(scene, targetTexture) {
- const meshes = [];
-
- scene.traverse((object) => {
- if (object.material) {
- const materials = Array.isArray(object.material) ? object.material : [object.material];
- for (const material of materials) {
- if (material.map === targetTexture) {
- meshes.push(object);
- break;
- }
- }
- }
- });
-
- return meshes;
- }
-
- function getAsset(path) {
- if (typeof path == "string") {
- //string?
- if (path.includes("/")) {
- //has the /?
- const value = path.split("/");
- console.log(value[0], value[1]);
- return assets[value[0]][value[1]];
- }
- }
-
- return JSON.parse(path); //boolean or number
- }
-
- let mouseNDC = [0, 0];
- //loops/init
- function stopLoop() {
- if (!running) return;
- running = false;
-
- if (loopId) {
- cancelAnimationFrame(loopId);
- loopId = null;
- if (threeRenderer) threeRenderer.clear();
- }
- }
- async function load() {
- if (!THREE) {
- // @ts-ignore
-<<<<<<< HEAD
- THREE = await import("https://esm.sh/three@0.180.0")
- window._THREE_ = THREE
-=======
- THREE = await import("https://esm.sh/three@0.180.0");
->>>>>>> e4a038b (Update threejsD.js)
- //Addons
- GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
- OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
- BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
- TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
- const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
- fontLoad = new FontLoader.FontLoader();
-
- const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
- const {
- EffectComposer,
- EffectPass,
- RenderPass,
-
- Effect,
- BloomEffect,
- GodRaysEffect,
- DotScreenEffect,
- DepthOfFieldEffect,
-
- BlendFunction,
- } = POSTPROCESSING;
- //so i can use them later as global
- window.EffectComposer = EffectComposer;
- window.EffectPass = EffectPass;
- window.RenderPass = RenderPass;
- window.Effect = Effect;
- window.BloomEffect = BloomEffect;
- window.GodRaysEffect = GodRaysEffect;
- window.DotScreenEffect = DotScreenEffect;
- window.DepthOfFieldEffect = DepthOfFieldEffect;
- window.BlendFunction = BlendFunction;
-
- RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
- await RAPIER.init();
-
- threeRenderer = new THREE.WebGLRenderer({
- powerPreference: "high-performance",
- antialias: false,
- stencil: false,
- depth: true,
- });
- threeRenderer.setPixelRatio(window.devicePixelRatio);
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
- //threeRenderer.toneMappingExposure = 1.0 //(test)
-
- threeRenderer.shadowMap.enabled = true;
- threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
- threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
-
- gltf = new GLTFLoader.GLTFLoader();
- clock = new THREE.Clock();
-
- // Example: create a composer
- composer = new EffectComposer(threeRenderer, {
- frameBufferType: THREE.HalfFloatType,
- });
-
- renderer.addOverlay(threeRenderer.domElement, "manual");
- renderer.addOverlay(canvas, "manual");
- renderer.setBackgroundColor(1, 1, 1, 0);
-
- resize();
-
- window.addEventListener("mousedown", (e) => {
- if (e.button === 0) isMouseDown.left = true;
- if (e.button === 1) isMouseDown.middle = true;
- if (e.button === 2) isMouseDown.right = true;
- });
- window.addEventListener("mouseup", (e) => {
- if (e.button === 0) isMouseDown.left = false;
- prevMouse.left = false;
- if (e.button === 1) isMouseDown.middle = false;
- prevMouse.middle = false;
- if (e.button === 2) isMouseDown.right = false;
- prevMouse.right = false;
- });
- // prevent contextmenu on right click
- threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
-
- threeRenderer.domElement.addEventListener("mousemove", (event) => {
- mouseNDC = getMouseNDC(event);
- });
-
- running = false;
- load();
-
-<<<<<<< HEAD
- startRenderLoop()
- runtime.on('PROJECT_START', () => startRenderLoop())
- runtime.on('PROJECT_STOP_ALL', () => stopLoop())
- runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
- checkCanvasSize()
-=======
- startRenderLoop();
- runtime.on("PROJECT_START", () => startRenderLoop());
- runtime.on("PROJECT_STOP_ALL", () => stopLoop());
- runtime.on("STAGE_SIZE_CHANGED", () => {
- requestAnimationFrame(() => resize());
- });
- //if (!runtime.isPackaged) checkCanvasSize() //only in editor
->>>>>>> e4a038b (Update threejsD.js)
- }
- }
-
- function startRenderLoop() {
- if (running) return;
- running = true;
-
- const loop = () => {
- if (!running) return;
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step();
-
- scene.children.forEach((obj) => {
- if (!obj.isMesh || !obj.physics) return;
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation());
- obj.quaternion.copy(obj.rigidBody.rotation());
- }
- });
- }
- if (scene && camera) {
- if (controls) controls.update();
-
- const delta = clock.getDelta();
- Object.values(models).forEach((model) => {
- if (model) model.mixer.update(delta);
- });
-
- Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
-
- //update custom effects time
- customEffects.forEach((e) => {
- if (e.uniforms.get("time")) {
- e.uniforms.get("time").value += delta;
- }
- });
- Object.values(renderTargets).forEach((t) => {
- if (t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height;
- t.camera.updateProjectionMatrix();
- }
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = false;
- });
-
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target);
- threeRenderer.clear(true, true, true);
- threeRenderer.render(scene, t.camera);
- } else {
- t.target.clear(threeRenderer);
- t.camera.update(threeRenderer, scene); //cubeCamera
- }
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = true;
- });
- });
-
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
- camera.updateProjectionMatrix();
- threeRenderer.setRenderTarget(null);
- composer.render(delta);
- }
-
- loopId = requestAnimationFrame(loop);
- };
-
- loopId = requestAnimationFrame(loop);
- }
-
- function resize() {
- const w = canvas.width;
- const h = canvas.height;
-
- threeRenderer.setSize(w, h);
- composer.setSize(w, h);
- customEffects.forEach((e) => {
- if (e.uniforms.get("resolution")) {
- e.uniforms.get("resolution").value.set(w, h);
- }
- });
-
- if (camera) {
- camera.aspect = w / h;
- camera.updateProjectionMatrix();
- }
- }
- //wait until all packages are loaded
- Promise.resolve(load()).then(() => {
- class threejsExtension {
- getInfo() {
- return {
- id: "threejsExtension",
- name: "Extra 3D",
- color1: "#222222",
- color2: "#222222",
- color3: "#11cc99",
- menuIconURI,
- blockIconURI: menuIconURI,
-
- blocks: [{
- blockType: Scratch.BlockType.BUTTON,
- text: "Show Docs",
- func: "openDocs",
- },
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Toggle Alerts",
- func: "alerts",
- },
- ],
- menus: {},
- };
- }
- openDocs() {
- open("https://civ3ro.github.io/extensions/Documentation/");
- }
- alerts() {
- alerts = !alerts;
- alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
- }
- }
- Scratch.extensions.register(new threejsExtension());
-
- class ThreeRenderer {
- getInfo() {
- return {
- id: "threeRenderer",
- name: "Three Renderer",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "setRendererRatio",
- blockType: Scratch.BlockType.COMMAND,
- text: "set Pixel Ratio to [VALUE]",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "eulerOrder",
- blockType: Scratch.BlockType.COMMAND,
- text: "set euler order to [VALUE]",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "YXZ",
- },
- },
- },
- ],
- menus: {},
- };
- }
-
- setRendererRatio(args) {
- threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
- }
- eulerOrder(args) {
- eulerOrder = args.VALUE;
- console.log("euler order set to", eulerOrder);
- }
- }
- Scratch.extensions.register(new ThreeRenderer());
-
- class ThreeScene {
- constructor() {
- this.THREE = THREE;
- this.scenes = {};
- }
-
- getInfo() {
- return {
- id: "threeScene",
- name: "Three Scene",
- color1: "#4638c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "newScene",
- blockType: Scratch.BlockType.COMMAND,
- text: "new Scene [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- },
- },
-
- {
- opcode: "setSceneProperty",
- blockType: Scratch.BlockType.COMMAND,
- text: "set Scene [PROPERTY] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "sceneProperties",
- defaultValue: "background",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "new Color()",
- exemptFromNormalization: true,
- },
- },
- },
- "---",
- {
- opcode: "getSceneObjects",
- blockType: Scratch.BlockType.REPORTER,
- text: "get Scene [THING]",
- arguments: {
- THING: {
- type: Scratch.ArgumentType.STRING,
- menu: "sceneThings",
- },
- },
- },
- {
- opcode: "reset",
- blockType: Scratch.BlockType.COMMAND,
- text: "Reset Everything",
- },
- ],
- menus: {
- sceneProperties: {
- acceptReporters: false,
- items: [{
- text: "Background",
- value: "background",
- },
- {
- text: "Background Blurriness",
- value: "backgroundBlurriness",
- },
- {
- text: "Background Intensity",
- value: "backgroundIntensity",
- },
- {
- text: "Background Rotation",
- value: "backgroundRotation",
- },
- {
- text: "Environment",
- value: "environment",
- },
- {
- text: "Environment Intensity",
- value: "environmentIntensity",
- },
- {
- text: "Environment Rotation",
- value: "environmentRotation",
- },
- {
- text: "Fog",
- value: "fog",
- },
- ],
- },
- sceneThings: {
- acceptReporters: false,
- items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
- },
- },
- };
- }
-
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME;
- scene.background = new THREE.Color("#222");
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {
- ...this.scenes,
- ...scene,
- };
- resetor(0);
- }
-
- reset() {
- resetor(1);
- }
-
- async setSceneProperty(args) {
- const property = args.PROPERTY;
- const value = getAsset(args.VALUE);
-
- scene[property] = value;
- }
- getSceneObjects(args) {
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse((obj) => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
- else if (args.THING === "Scene Properties") {
- console.log(scene);
- return "check console";
- } else if (args.THING === "Other assets") return JSON.stringify(assets);
-
- return JSON.stringify(names); // if objects
- }
- }
- Scratch.extensions.register(new ThreeScene());
-
- class ThreeCameras {
- getInfo() {
- return {
- id: "threeCameras",
- name: "Three Cameras",
- color1: "#38c59bff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "add camera [TYPE] [CAMERA] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraTypes",
- },
- },
- },
- {
- opcode: "setCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraProperties",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "0.1",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "getCamera",
- blockType: Scratch.BlockType.REPORTER,
- text: "get camera [PROPERTY] of [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraProperties",
- },
- },
- },
- "---",
- {
- opcode: "renderSceneCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "set rendering camera to [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- },
- },
- "---",
- {
- opcode: "cubeCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "cubeCamera",
- },
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- "---",
- {
- opcode: "renderTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "set a RenderTarget: [RT] for camera [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- {
- opcode: "sizeTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "set RenderTarget [RT] size to [W] [H]",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- W: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 480,
- },
- H: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 360,
- },
- },
- },
- {
- opcode: "getTarget",
- blockType: Scratch.BlockType.REPORTER,
- text: "get RenderTarget: [RT] texture",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- {
- opcode: "removeTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "remove RenderTarget: [RT]",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- ],
- menus: {
- cameraTypes: {
- acceptReporters: false,
- items: [{
- text: "Perspective",
- value: "PerspectiveCamera",
- }, ],
- },
- cameraProperties: {
- acceptReporters: false,
- items: [{
- text: "Near",
- value: "near",
- },
- {
- text: "Far",
- value: "far",
- },
- {
- text: "FOV",
- value: "fov",
- },
- {
- text: "Focus (nothing...)",
- value: "focus",
- },
- {
- text: "Zoom",
- value: "zoom",
- },
- ],
- },
- },
- };
- }
- addCamera(args) {
- let v2 = new THREE.Vector2();
- threeRenderer.getSize(v2);
- const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
- object.position.z = 3;
-
- createObject(args.CAMERA, object, args.GROUP);
- }
- setCamera(args) {
- let object = getObject(args.CAMERA);
- object[args.PROPERTY] = args.VALUE;
- object.updateProjectionMatrix();
- }
- getCamera(args) {
- let object = getObject(args.CAMERA);
- const value = JSON.stringify(object[args.PROPERTY]);
- return value;
- }
- renderSceneCamera(args) {
- let object = getObject(args.CAMERA);
- if (!object) return;
- camera = object;
- //reset composer, else it does not update.
- composer.passes = [];
- passes = {};
- customEffects = [];
- updateComposers();
- }
-
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
- generateMipmaps: true,
- });
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
- createObject(args.CAMERA, cubeCamera, args.GROUP);
-
- renderTargets[args.RT] = {
- target: cubeRenderTarget,
- camera: cubeCamera,
- };
- assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
- }
-
- renderTarget(args) {
- let object = getObject(args.CAMERA);
- const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
- generateMipmaps: false,
- });
-
- renderTargets[args.RT] = {
- target: renderTarget,
- camera: object,
- };
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
- }
- sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H);
- }
- getTarget(args) {
- const t = renderTargets[args.RT].target.texture;
- console.log(t, renderTargets[args.RT]);
- return `renderTargets/${t.uuid}`;
- }
- removeTarget(args) {
- delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
- renderTargets[args.RT].target.dispose();
- delete renderTargets[args.RT];
- }
- }
- Scratch.extensions.register(new ThreeCameras());
-
- class ThreeObjects {
- getInfo() {
- return {
- id: "threeObjects",
- name: "Three Objects",
- color1: "#38c567ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "add object [OBJECT3D] [TYPE] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectTypes",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "cloneObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myClone",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "setObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectProperties",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- {
- opcode: "getObject",
- blockType: Scratch.BlockType.REPORTER,
- text: "get [PROPERTY] of object [OBJECT3D]",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectProperties",
- },
- },
- },
- {
- opcode: "objectE",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there an object [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "removeObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "remove object [OBJECT3D] from scene",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: " ↳ Transforms",
- },
- {
- opcode: "setObjectV3",
- extensions: ["colours_motion"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectVector3",
- defaultValue: "position",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
- //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {
- opcode: "getObjectV3",
- extensions: ["colours_motion"],
- blockType: Scratch.BlockType.REPORTER,
- text: "get [PROPERTY] of [OBJECT3D]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectVector3",
- defaultValue: "position",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Materials",
- },
- {
- opcode: "newMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new material [NAME] [TYPE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "materialTypes",
- defaultValue: "MeshStandardMaterial",
- },
- },
- },
- {
- opcode: "materialE",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there a material [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- },
- },
- {
- opcode: "removeMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "remove material [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- },
- },
- {
- opcode: "setMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [PROPERTY] of [NAME] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "materialProperties",
- defaultValue: "color",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "new Color()",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "setBlending",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [NAME] blending to [VALUE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- },
- },
- },
- {
- opcode: "setDepth",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [NAME] depth to [VALUE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- menu: "depthModes",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Geometries",
- },
- {
- opcode: "newGeometry",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new geometry [NAME] [TYPE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "geometryTypes",
- defaultValue: "BoxGeometry",
- },
- },
- },
- {
- opcode: "geometryE",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there a geometry [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- {
- opcode: "removeGeometry",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "remove geometry [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- "---",
- {
- opcode: "newGeo",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new empty geometry [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[points]",
- },
- },
- },
- {
- opcode: "geoPoints",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set geometry [NAME] vertex points to [POINTS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[points]",
- },
- },
- },
- {
- opcode: "geoUVs",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set geometry [NAME] UVs to [POINTS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[UVs]",
- },
- },
- },
- "---",
- {
- opcode: "splines",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create spline [NAME] from curve [CURVE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySpline",
- },
- CURVE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[curve]",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "splineModel",
- extensions: ["colours_operators"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySpline",
- },
- MODEL: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelsList",
- },
- CURVE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[curve]",
- exemptFromNormalization: true,
- },
- SPACING: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Convert font to JSON",
- func: "openConv",
- },
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Load JSON font file",
- func: "loadFont",
- },
- {
- opcode: "text",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myText",
- },
- TEXT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "C-369",
- },
- FONT: {
- type: Scratch.ArgumentType.STRING,
- menu: "fonts",
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- D: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.1,
- },
- CS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 6,
- },
- },
- },
- ],
- menus: {
- objectVector3: {
- acceptReporters: false,
- items: [{
- text: "Positon",
- value: "position",
- },
- {
- text: "Rotation",
- value: "rotation",
- },
- {
- text: "Scale",
- value: "scale",
- },
- {
- text: "Facing Direction (.up)",
- value: "up",
- },
- ],
- },
- objectProperties: {
- acceptReporters: false,
- items: [{
- text: "Geometry",
- value: "geometry",
- },
- {
- text: "Material",
- value: "material",
- },
- {
- text: "Visible (true/false)",
- value: "visible",
- },
- ],
- },
- objectTypes: {
- acceptReporters: false,
- items: [{
- text: "Mesh",
- value: "Mesh",
- },
- {
- text: "Sprite",
- value: "Sprite",
- },
- {
- text: "Points",
- value: "Points",
- },
- {
- text: "Line",
- value: "Line",
- },
- {
- text: "Group",
- value: "Group",
- },
- ],
- },
- XYZ: {
- acceptReporters: false,
- items: [{
- text: "X",
- value: "x",
- },
- {
- text: "Y",
- value: "y",
- },
- {
- text: "Z",
- value: "z",
- },
- ],
- },
- materialProperties: {
- acceptReporters: false,
- items: [
- "|GENERAL| <-- not a property",
- {
- text: "Color",
- value: "color",
- },
- {
- text: "Map",
- value: "map",
- },
- {
- text: "Opacity",
- value: "opacity",
- },
- {
- text: "Transparent",
- value: "transparent",
- },
- {
- text: "Alpha Map",
- value: "alphaMap",
- },
- {
- text: "Alpha Test",
- value: "alphaTest",
- },
- {
- text: "Depth Test",
- value: "depthTest",
- },
- {
- text: "Depth Write",
- value: "depthWrite",
- },
- {
- text: "Color Write",
- value: "colorWrite",
- },
- {
- text: "Side",
- value: "side",
- },
- {
- text: "Visible",
- value: "visible",
- },
- /*
- { text: "Blending", value: "blending" },
- { text: "Blend Src", value: "blendSrc" },
- { text: "Blend Dst", value: "blendDst" },
- { text: "Blend Equation", value: "blendEquation" },
- { text: "Blend Src Alpha", value: "blendSrcAlpha" },
- { text: "Blend Dst Alpha", value: "blendDstAlpha" },
- { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
- {
- text: "Blend Aplha",
- value: "blendAplha",
- },
- {
- text: "Blend Color",
- value: "blendColor",
- },
- {
- text: "Alpha Hash",
- value: "alphaHash",
- },
- {
- text: "Premultiplied Alpha",
- value: "premultipliedAlpha",
- },
-
- {
- text: "Tone Mapped",
- value: "toneMapped",
- },
- {
- text: "Fog",
- value: "fog",
- },
- {
- text: "Flat Shading",
- value: "flatShading",
- },
-
- "|MESH Standard / Physical| <-- not a property",
- {
- text: "Metalness",
- value: "metalness",
- },
- {
- text: "Metalness Map",
- value: "metalnessMap",
- },
- {
- text: "Roughness",
- value: "roughness",
- },
- {
- text: "Reflectivity",
- value: "reflectivity",
- },
- {
- text: "Roughness Map",
- value: "roughnessMap",
- },
- {
- text: "Emissive",
- value: "emissive",
- },
- {
- text: "Emissive Intensity",
- value: "emissiveIntensity",
- },
- {
- text: "Emissive Map",
- value: "emissiveMap",
- },
- {
- text: "Env Map",
- value: "envMap",
- },
- {
- text: "Env Map Intensity",
- value: "envMapIntensity",
- },
- {
- text: "Env Map Rotation",
- value: "envMapRotation",
- },
- {
- text: "Ior",
- value: "ior",
- },
- {
- text: "Refraction Ratio",
- value: "refractionRatio",
- },
- {
- text: "Clearcoat",
- value: "clearcoat",
- },
- {
- text: "Clearcoat Map",
- value: "clearcoatMap",
- },
- {
- text: "Clearcoat Roughness",
- value: "clearcoatRoughness",
- },
- {
- text: "Clearcoat Roughness Map",
- value: "clearcoatRoughnessMap",
- },
- {
- text: "Dispersion",
- value: "dispersion",
- },
- {
- text: "Sheen",
- value: "sheen",
- },
- {
- text: "Sheen Color",
- value: "sheenColor",
- },
- {
- text: "Sheen Color Map",
- value: "sheenColorMap",
- },
- {
- text: "Sheen Roughness",
- value: "sheenRoughness",
- },
- {
- text: "Sheen Roughness Map",
- value: "sheenRoughnessMap",
- },
- {
- text: "Specular Color",
- value: "specularColor",
- },
- {
- text: "Specular Color Map",
- value: "specularColorMap",
- },
- {
- text: "Specular Intensity",
- value: "specularIntensity",
- },
- {
- text: "Specular Intensity Map",
- value: "specularIntensityMap",
- },
- {
- text: "Transmission",
- value: "transmission",
- },
- {
- text: "Transmission Map",
- value: "transmissionMap",
- },
- {
- text: "Thickness",
- value: "thickness",
- },
- {
- text: "Thickness Map",
- value: "thicknessMap",
- },
- {
- text: "Anisotropy",
- value: "anisotropy",
- },
- {
- text: "Anisotropy Map",
- value: "anisotropyMap",
- },
- {
- text: "Anisotropy Rotation",
- value: "anisotropyRotation",
- },
- {
- text: "Attenuation Distance",
- value: "attenuationDistance",
- },
- {
- text: "Attenuation Color",
- value: "attenuationColor",
- },
- {
- text: "Thickness",
- value: "thickness",
- },
- {
- text: "Iridescence",
- value: "iridescence",
- },
- {
- text: "Iridescence Ior",
- value: "iridescenceIOR",
- },
- {
- text: "Iridescence Map",
- value: "iridescenceMap",
- },
- {
- text: "Iridescence Thickness Range",
- value: "iridescenceThicknessRange",
- },
-
- "|MESH Displacement / Normal / Bump| <-- not a property",
- {
- text: "Displacement Map",
- value: "displacementMap",
- },
- {
- text: "Displacement Scale",
- value: "displacementScale",
- },
- {
- text: "Displacement Bias",
- value: "displacementBias",
- },
- {
- text: "Bump Map",
- value: "bumpMap",
- },
- {
- text: "Bump Scale",
- value: "bumpScale",
- },
- {
- text: "Normal Map Type",
- value: "normalMapType",
- },
-
- "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
- {
- text: "Shininess",
- value: "shininess",
- },
-
- {
- text: "Wireframe",
- value: "wireframe",
- },
- {
- text: "Wireframe Linewidth",
- value: "wireframeLinewidth",
- },
- {
- text: "Wireframe Linecap",
- value: "wireframeLinecap",
- },
- {
- text: "Wireframe Linejoin",
- value: "wireframeLinejoin",
- },
-
- "|POINTS| <-- not a property",
- {
- text: "Size",
- value: "size",
- },
- {
- text: "Size Attenuation",
- value: "sizeAttenuation",
- },
-
- "|LINES| <-- not a property",
- {
- text: "Scale",
- value: "scale",
- },
- {
- text: "Dash Size",
- value: "dashSize",
- },
- {
- text: "Gap Size",
- value: "gapSize",
- },
-
- "|SPRITES| <-- not a property",
- {
- text: "Rotation",
- value: "rotation",
- },
- ],
- },
- blendModes: {
- acceptReporters: false,
- items: [{
- text: "No Blending",
- value: "NoBlending",
- },
- {
- text: "Normal Blending",
- value: "NormalBlending",
- },
- {
- text: "Additive Blending",
- value: "AdditiveBlending",
- },
- {
- text: "Subtractive Blending",
- value: "SubtractiveBlending",
- },
- {
- text: "Multiply Blending",
- value: "MultiplyBlending",
- },
- {
- text: "Custom Blending",
- value: "CustomBlending",
- },
- ],
- },
- depthModes: {
- acceptReporters: false,
- items: [{
- text: "Never Depth",
- value: "NeverDepth",
- },
- {
- text: "Always Depth",
- value: "AlwaysDepth",
- },
- {
- text: "Equal Depth",
- value: "EqualDepth",
- },
- {
- text: "Less Depth",
- value: "LessDepth",
- },
- {
- text: "Less Equal Depth",
- value: "LessEqualDepth",
- },
- {
- text: "Greater Equal Depth",
- value: "GreaterEqualDepth",
- },
- {
- text: "Greater Depth",
- value: "GreaterDepth",
- },
- {
- text: "Not Equal Depth",
- value: "NotEqualDepth",
- },
- ],
- },
- materialTypes: {
- acceptReporters: false,
- items: [{
- text: "Mesh Basic Material",
- value: "MeshBasicMaterial",
- },
- {
- text: "Mesh Standard Material",
- value: "MeshStandardMaterial",
- },
- {
- text: "Mesh Physical Material",
- value: "MeshPhysicalMaterial",
- },
- {
- text: "Mesh Lambert Material",
- value: "MeshLambertMaterial",
- },
- {
- text: "Mesh Phong Material",
- value: "MeshPhongMaterial",
- },
- {
- text: "Mesh Depth Material",
- value: "MeshDepthMaterial",
- },
- {
- text: "Mesh Normal Material",
- value: "MeshNormalMaterial",
- },
- {
- text: "Mesh Matcap Material",
- value: "MeshMatcapMaterial",
- },
- {
- text: "Mesh Toon Material",
- value: "MeshToonMaterial",
- },
- {
- text: "Line Basic Material",
- value: "LineBasicMaterial",
- },
- {
- text: "Line Dashed Material",
- value: "LineDashedMaterial",
- },
- {
- text: "Points Material",
- value: "PointsMaterial",
- },
- {
- text: "Sprite Material",
- value: "SpriteMaterial",
- },
- {
- text: "Shadow Material",
- value: "ShadowMaterial",
- },
- ],
- },
- textureModes: {
- acceptReporters: false,
- items: ["Pixelate", "Blur"],
- },
- textureStyles: {
- acceptReporters: false,
- items: ["Repeat", "Clamp"],
- },
- geometryTypes: {
- acceptReporters: false,
- items: [{
- text: "Box Geometry",
- value: "BoxGeometry",
- },
- {
- text: "Sphere Geometry",
- value: "SphereGeometry",
- },
- {
- text: "Cylinder Geometry",
- value: "CylinderGeometry",
- },
- {
- text: "Plane Geometry",
- value: "PlaneGeometry",
- },
- {
- text: "Circle Geometry",
- value: "CircleGeometry",
- },
- {
- text: "Torus Geometry",
- value: "TorusGeometry",
- },
- {
- text: "Torus Knot Geometry",
- value: "TorusKnotGeometry",
- },
- ],
- },
- modelsList: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".glb"));
- if (models.length < 1) return [
- ["Load a model! (GLB Loader category)"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- fonts: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".json"));
- if (models.length < 1) return [
- ["Load a font!"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- },
- };
- }
-
- addObject(args) {
- const object = new THREE[args.TYPE]();
-
- object.castShadow = true;
- object.receiveShadow = true;
-
- createObject(args.OBJECT3D, object, args.GROUP);
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D);
- const clone = object.clone(true);
- clone.name;
- createObject(args.NAME, clone, args.GROUP);
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D);
- let values = JSON.parse(args.VALUE);
-
- function degToRad(deg) {
- return (deg * Math.PI) / 180;
- }
-
- if (object.rigidBody) {
- const x = values[0];
- const y = values[1];
- const z = values[2];
- if (args.PROPERTY === "rotation") {
- const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
- const quaternion = new THREE.Quaternion();
- quaternion.setFromEuler(euler);
-
- object.rigidBody.setRotation({
- x: quaternion.x,
- y: quaternion.y,
- z: quaternion.z,
- w: quaternion.w,
- });
- } else if (args.PROPERTY === "position") {
- object.rigidBody.setTranslation({
- x: x,
- y: y,
- z: z,
- },
- true
- );
- }
- return;
- }
-
- if (object.isCamera == true && controls) {}
-
- if (args.PROPERTY === "rotation") {
- values = values.map((v) => (v * Math.PI) / 180);
- object.rotation.set(0, 0, 0);
- }
- if (object.isDirectionalLight == true) {
- object.pos = new THREE.Vector3(...values);
- console.log(true, values, object.pos);
- return;
- }
- object[args.PROPERTY].set(...values);
-
- if (object.type == "CubeCamera") object.updateCoordinateSystem();
- }
- /*
- changeObjectV3(args) {
- getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
-
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.x += values[0]
- object.rotation.y += values[1]
- object.rotation.z += values[2]
- }
- else {
- object[args.PROPERTY].add(...values);
- }
- }
- changeObjectXV3(args) {
- getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "rotation") value = value * Math.PI / 180
-
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D);
- if (!object) return;
- let values = vector3ToString(object[args.PROPERTY]);
- if (args.PROPERTY === "rotation") {
- const toDeg = Math.PI / 180;
- values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
- }
-
- return JSON.stringify(values);
- }
- setObject(args) {
- let object = getObject(args.OBJECT3D);
- let value = args.VALUE;
- if (args.PROPERTY === "material") {
- const mat = materials[args.NAME];
- if (mat) value = mat;
- else value = undefined;
- } else if (args.PROPERTY === "geometry") {
- const geo = geometries[args.NAME];
- if (geo) value = geo;
- else value = undefined;
- } else value = !!value;
-
- if (value == undefined) return; //invalid geo/mat
- object[args.PROPERTY] = value;
- }
- getObject(args) {
- let object = getObject(args.OBJECT3D);
- if (!object) return;
- let value;
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value;
- }
- removeObject(args) {
- removeObject(args.OBJECT3D);
- }
- objectE(args) {
- return scene.children.map((o) => o.name).includes(args.NAME);
- }
-
- //defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
-
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
- const mat = materials[args.NAME];
-
- let value = args.VALUE;
-
- if (args.VALUE == "false") value = false;
-
- if (args.PROPERTY == "side") {
- value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
- } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
- else value = getAsset(value);
-
- console.log("o:", args.VALUE, typeof args.VALUE);
- console.log("r:", value, typeof value);
-
- mat[args.PROPERTY] = await value; //await incase its a texture
- mat.needsUpdate = true;
- }
- setBlending(args) {
- const mat = materials[args.NAME];
- mat.blending = THREE[args.VALUE];
- mat.premultipliedAlpha = true;
- mat.needsUpdate = true;
- }
- setDepth(args) {
- const mat = materials[args.NAME];
- mat.depthFunc = THREE[args.VALUE];
- mat.needsUpdate = true;
- }
- removeMaterial(args) {
- const mat = materials[args.NAME];
- mat.dispose();
- delete materials[args.NAME];
- }
- materialE(args) {
- return materials[args.NAME] ? true : false;
- }
-
- newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
- const geo = new THREE[args.TYPE]();
- geo.name = args.NAME;
-
- geometries[args.NAME] = geo;
- }
- setGeometry(args) {
- const geo = geometries[args.NAME];
- geo[args.PROPERTY] = args.VALUE;
-
- geo.needsUpdate = true;
- }
- removeGeometry(args) {
- const geo = geometries[args.NAME];
- geo.dispose();
- delete geometries[args.NAME];
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false;
- }
-
- newGeo(args) {
- const geometry = new THREE.BufferGeometry();
- geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME];
- const positions = args.POINTS.split(" ")
- .map((v) => JSON.parse(v))
- .flat(); //array of v3 of each vertex of each triangle
-
- geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
- geometry.computeVertexNormals();
-
- geometry.needsUpdate = true;
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME];
- const UVs = args.POINTS.split(" ")
- .map((v) => JSON.parse(v))
- .flat(); //array of v2 of each UV of each triangle
-
- geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
- geometry.needsUpdate = true;
- }
-
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
- geometry.name = args.NAME;
-
- geometries[args.NAME] = geometry;
- }
-
- async splineModel(args) {
- const model = await getModel(args.MODEL, args.NAME);
- if (!model) return console.warn("Model not found:", args.MODEL);
-
- const curve = getAsset(args.CURVE);
- const spacing = parseFloat(args.SPACING) || 1;
- const curveLength = curve.getLength();
- const divisions = Math.floor(curveLength / spacing);
-
- const geomList = [];
- const matList = [];
-
- for (let i = 0; i <= divisions; i++) {
- const t = i / divisions;
- const pos = curve.getPointAt(t);
- const tangent = curve.getTangentAt(t);
-
- const temp = model.clone(true);
- temp.position.copy(pos);
-
- const up = new THREE.Vector3(0, 0, 1);
- const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
- temp.quaternion.copy(quat);
-
- temp.updateMatrixWorld(true);
-
- temp.traverse((child) => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone();
- geom.applyMatrix4(child.matrixWorld);
- geomList.push(geom);
- matList.push(child.material); //.clone() ?
- }
- });
- }
-
- const validGeoms = geomList.filter((g) => {
- const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
- if (!ok) console.warn("geometry skipped:", g);
- return ok;
- });
-
- const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
- merged.computeBoundingBox();
- merged.computeBoundingSphere();
-
- merged.name = args.NAME;
- geometries[args.NAME] = merged;
- matList.name = args.NAME;
- materials[args.NAME] = matList;
- }
-
- async text(args) {
- const fontFile = runtime
- .getTargetForStage()
- .getSounds()
- .find((c) => c.name === args.FONT);
- if (!fontFile) return;
-
- const json = new TextDecoder().decode(fontFile.asset.data.buffer);
- const fontData = JSON.parse(json);
-
- const font = fontLoad.parse(fontData);
-
- const params = {
- font: font,
- size: JSON.parse(args.S),
- height: JSON.parse(args.D),
- curveSegments: JSON.parse(args.CS),
- bevelEnabled: false,
- };
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
- geometry.computeVertexNormals();
- geometry.center(); // optional, recenters the text
-
- geometry.name = args.NAME;
-
- geometries[args.NAME] = geometry;
- }
-
- async loadFont() {
- openFileExplorer(".json").then((files) => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- // From lily's assets
- // // Thank you PenguinMod for providing this code.
-
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- const buffer = arrayBuffer;
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Font loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading font.");
- }
-
- // End of PenguinMod
- };
-
- reader.readAsArrayBuffer(file);
- });
- }
- openConv() {
- {
- open("https://gero3.github.io/facetype.js/");
- }
- }
- }
- Scratch.extensions.register(new ThreeObjects());
-
- class ThreeLights {
- getInfo() {
- return {
- id: "threeLights",
- name: "Three Lights",
- color1: "#c7a22aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addLight",
- blockType: Scratch.BlockType.COMMAND,
- text: "add light [NAME] type [TYPE] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myLight",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "lightTypes",
- },
- },
- },
- {
- opcode: "setLight",
- blockType: Scratch.BlockType.COMMAND,
- text: "set light [NAME][PROPERTY] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "lightProperties",
- defaultValue: "intensity",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myLight",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- exemptFromNormalization: true,
- },
- },
- },
- ],
- menus: {
- lightTypes: {
- acceptReporters: false,
- items: [{
- text: "Ambient Light",
- value: "AmbientLight",
- },
- {
- text: "Directional Light",
- value: "DirectionalLight",
- },
- {
- text: "Point Light",
- value: "PointLight",
- },
- {
- text: "Hemisphere Light",
- value: "HemisphereLight",
- },
- {
- text: "Spot Light",
- value: "SpotLight",
- },
- ],
- },
- lightProperties: {
- acceptReporters: false,
- items: [{
- text: "Color",
- value: "color",
- },
- {
- text: "Intensity",
- value: "intensity",
- },
- {
- text: "Cast Shadow?",
- value: "castShadow",
- },
- {
- text: "Ground Color (HemisphereLight)",
- value: "groundColor",
- },
- {
- text: "Map (SpotLight)",
- value: "map",
- },
- {
- text: "Distance (SpotLight)",
- value: "distance",
- },
- {
- text: "Decay (SpotLight)",
- value: "decay",
- },
- {
- text: "Penumbra (SpotLight)",
- value: "penumbra",
- },
- {
- text: "Angle/Size (SpotLight)",
- value: "angle",
- },
- {
- text: "Power (SpotLight)",
- value: "power",
- },
- {
- text: "Target Position (Directional/SpotLight)",
- value: "target",
- },
- ],
- },
- },
- };
- }
-
- addLight(args) {
- const light = new THREE[args.TYPE](0xffffff, 1);
-
- createObject(args.NAME, light, args.GROUP);
- lights[args.NAME] = light;
- if (light.type === "AmbientLight" || "HemisphereLight") return;
-
- light.castShadow = true;
- if (light.type === "PointLight") return;
- //Directional & Spot Light
- light.target.position.set(0, 0, 0);
- scene.add(light.target);
-
- light.pos = new THREE.Vector3(0, 0, 0);
-
- light.shadow.mapSize.width = 4096;
- light.shadow.mapSize.height = 2048;
-
- if (light.type === "SpotLight") {
- light.decay = 0;
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true;
- light.needsUpdate = true;
- }
-
- setLight(args) {
- const light = lights[args.NAME];
- if (!args.PROPERTY) return;
- if (args.PROPERTY === "target") {
- light.target.position.set(...JSON.parse(args.VALUE)); //vector3
- light.target.updateMatrixWorld();
- } else {
- light[args.PROPERTY] = getAsset(args.VALUE);
- }
- light.needsUpdate = true;
-
- if (light.type === "AmbientLight" || "HemisphereLight") return;
-
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true;
- }
- }
- Scratch.extensions.register(new ThreeLights());
-
- class ThreeUtilities {
- getInfo() {
- return {
- id: "threeUtility",
- name: "Three Utilities",
- color1: "#3875c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "newVector2",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Vector [X] [Y]",
- arguments: {
- X: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- },
- },
- },
- {
- opcode: "newVector3",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Vector [X] [Y] [Z]",
- arguments: {
- X: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Z: {
- type: Scratch.ArgumentType.NUMBER,
- },
- },
- },
- "---",
- {
- opcode: "operateV3",
- blockType: Scratch.BlockType.REPORTER,
- text: "do [V3] [O] [V32]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- O: {
- type: Scratch.ArgumentType.STRING,
- menu: "operators",
- },
- V32: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- {
- opcode: "moveVector3",
- blockType: Scratch.BlockType.REPORTER,
- text: "move [S] steps in vector [V3] in direction [D3]",
- arguments: {
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- D3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- {
- opcode: "directionTo",
- blockType: Scratch.BlockType.REPORTER,
- text: "direction from [V3] to [T3]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,3]",
- },
- T3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- "---",
- {
- opcode: "newColor",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Color [HEX]",
- arguments: {
- HEX: {
- type: Scratch.ArgumentType.COLOR,
- defaultValue: "#9966ff",
- },
- },
- },
- {
- opcode: "newFog",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Fog [COLOR] [NEAR] [FAR]",
- arguments: {
- COLOR: {
- type: Scratch.ArgumentType.COLOR,
- defaultValue: "#9966ff",
- exemptFromNormalization: true,
- },
- NEAR: {
- type: Scratch.ArgumentType.NUMBER,
- },
- FAR: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 10,
- },
- },
- },
- {
- opcode: "newTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
- arguments: {
- COSTUME: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- STYLE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureStyles",
- },
- X: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- {
- opcode: "newCubeTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
- arguments: {
- COSTUMEX0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEX1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEY0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEY1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEZ0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEZ1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- STYLE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureStyles",
- },
- X: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- {
- opcode: "newEquirectangularTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Equirectangular Texture [COSTUME] [MODE]",
- arguments: {
- COSTUME: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- },
- },
- "---",
- {
- opcode: "curve",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.REPORTER,
- text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
- arguments: {
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "curveTypes",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
- },
- CLOSED: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "true",
- },
- },
- },
- "---",
- {
- opcode: "mouseDown",
- extensions: ["colours_sensing"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "mouse [BUTTON] [action]?",
- arguments: {
- BUTTON: {
- type: Scratch.ArgumentType.STRING,
- menu: "mouseButtons",
- },
- action: {
- type: Scratch.ArgumentType.STRING,
- menu: "mouseAction",
- },
- },
- },
- {
- opcode: "mousePos",
- extensions: ["colours_sensing"],
- blockType: Scratch.BlockType.REPORTER,
- text: "mouse position",
- arguments: {},
- },
- "---",
- {
- opcode: "getItem",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.REPORTER,
- text: "get item [ITEM] of [ARRAY]",
- arguments: {
- ITEM: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- ARRAY: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: `["myObject", "myLight"]`,
- },
- },
- },
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Raycasting",
- },
- {
- opcode: "raycast",
- blockType: Scratch.BlockType.COMMAND,
- text: "Raycast from [V3] in direction [D3]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,3]",
- },
- D3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,1]",
- },
- },
- },
- {
- opcode: "getRaycast",
- blockType: Scratch.BlockType.REPORTER,
- text: "get raycast [PROPERTY]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "raycastProperties",
- },
- },
- },
- ],
- menus: {
- materialProperties: {
- acceptReporters: false,
- items: [{
- text: "Color",
- value: "color",
- },
- {
- text: "Map (texture)",
- value: "map",
- },
- {
- text: "Alpha Map (texture)",
- value: "alphaMap",
- },
- {
- text: "Alpha Test (0-1)",
- value: "alphaTest",
- },
- {
- text: "Side (front/back/double)",
- value: "side",
- },
- {
- text: "Bump Map (texture)",
- value: "bumpMap",
- },
- {
- text: "Bump Scale",
- value: "bumpScale",
- },
- ],
- },
- textureModes: {
- acceptReporters: false,
- items: ["Pixelate", "Blur"],
- },
- textureStyles: {
- acceptReporters: false,
- items: ["Repeat", "Clamp"],
- },
- raycastProperties: {
- acceptReporters: false,
- items: [{
- text: "Intersected Object Names",
- value: "name",
- },
- {
- text: "Number of Objects",
- value: "number",
- },
- {
- text: "Intersected Objects distances",
- value: "distance",
- },
- ],
- },
- mouseButtons: {
- acceptReporters: false,
- items: ["left", "middle", "right"],
- },
- mouseAction: {
- acceptReporters: false,
- items: ["Down", "Clicked"],
- },
- curveTypes: {
- acceptReporters: false,
- items: ["CatmullRomCurve3"],
- },
- operators: {
- acceptReporters: false,
- items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
- },
- },
- };
- }
- mouseDown(args) {
- if (args.action === "Down") return isMouseDown[args.BUTTON];
- if (args.action === "Clicked") {
- if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
- else prevMouse[args.BUTTON] = true;
- return true;
- }
- }
- mousePos(event) {
- return JSON.stringify(mouseNDC);
- }
- newVector3(args) {
- return JSON.stringify([args.X, args.Y, args.Z]);
- }
- operateV3(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3));
- const v32 = new THREE.Vector3(...JSON.parse(args.V32));
-
- let r;
- if (args.O == "+") r = v3.add(v32);
- else if (args.O == "-") r = v3.sub(v32);
- else if (args.O == "*") r = v3.multiply(v32);
- else if (args.O == "/") r = v3.divide(v32);
- else if (args.O == "=") r = v3.equals(v32);
- else if (args.O == "max") r = v3.max(v32);
- else if (args.O == "min") r = v3.min(v32);
- else if (args.O == "dot") r = v3.dot(v32);
- else if (args.O == "cross") r = v3.cross(v32);
- else if (args.O == "distance to") r = v3.distanceTo(v32);
- else if (args.O == "angle to") r = v3.angleTo(v32);
- else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
-
- if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
- else return JSON.stringify(r);
- }
-
- newVector2(args) {
- return JSON.stringify([args.X, args.Y]);
- }
-
- moveVector3(args) {
- const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
- const steps = Number(args.S);
-
- const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
-
- const yaw = THREE.MathUtils.degToRad(yawInputDeg);
- const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
- const roll = THREE.MathUtils.degToRad(rollInputDeg);
-
- const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
-
- const forwardVector = new THREE.Vector3(0, 0, -1);
- const direction = forwardVector.applyEuler(euler).normalize();
-
- const newPos = currentPos.add(direction.multiplyScalar(steps));
- return JSON.stringify([newPos.x, newPos.y, newPos.z]);
- }
-
- directionTo(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3));
- const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
-
- const direction = toV3.clone().sub(v3).normalize();
- // Pitch (X)
- const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
- // Yaw (Y)
- const yaw = Math.atan2(direction.x, direction.z);
-
- // Roll always 0
- return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
- }
-
- newColor(args) {
- const color = new THREE.Color(args.HEX);
- const uuid = crypto.randomUUID();
- assets.colors[uuid] = color;
- return `colors/${uuid}`;
- }
- newFog(args) {
- const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
- const uuid = crypto.randomUUID();
- assets.fogs[uuid] = fog;
- return `fogs/${uuid}`;
- }
- async newTexture(args) {
- const textureURI = encodeCostume(args.COSTUME);
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
- async newCubeTexture(args) {
- const uris = [
- encodeCostume(args.COSTUMEX0),
- encodeCostume(args.COSTUMEX1),
- encodeCostume(args.COSTUMEY0),
- encodeCostume(args.COSTUMEY1),
- encodeCostume(args.COSTUMEZ0),
- encodeCostume(args.COSTUMEZ1),
- ];
- const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME);
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME;
- texture.mapping = THREE.EquirectangularReflectionMapping;
-
- setTexutre(texture, args.MODE);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
-
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g);
- if (!matches) return [];
-
- return matches.map((str) => {
- const nums = str
- .replace(/[\[\]\s]/g, "")
- .split(",")
- .map(Number);
- return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
- });
- }
- const points = parsePoints(args.POINTS);
- const curve = new THREE[args.TYPE](points);
- curve.closed = JSON.parse(args.CLOSED);
-
- const uuid = crypto.randomUUID();
- assets.curves[uuid] = curve;
- return `curves/${uuid}`;
- }
-
- getItem(args) {
- const items = JSON.parse(args.ARRAY);
- const item = items[args.ITEM - 1];
- if (!item) return "0";
- return item;
- }
-
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3));
- // rotation is in degrees => convert to radians first
- const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
-
- const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
- const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
-
- const raycaster = new THREE.Raycaster();
- //const camera = getObject(args.CAMERA)
- raycaster.set(origin, direction);
-
- const intersects = raycaster.intersectObjects(scene.children, true);
-
- raycastResult = intersects;
- }
- getRaycast(args) {
- if (args.PROPERTY === "number") return raycastResult.length;
- if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
- return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
- }
- }
- Scratch.extensions.register(new ThreeUtilities());
-
- class ThreeGLB {
- getInfo() {
- return {
- id: "threeGLB",
- name: "Three GLB Loader",
- color1: "#c53838ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- blockType: Scratch.BlockType.BUTTON,
- text: "Load GLB File",
- func: "loadModelFile",
- },
- {
- opcode: "addModel",
- blockType: Scratch.BlockType.COMMAND,
- text: "add [ITEM] as [NAME] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- ITEM: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelsList",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- },
- },
- {
- opcode: "getModel",
- blockType: Scratch.BlockType.REPORTER,
- text: "get object [PROPERTY] of [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelProperties",
- },
- },
- },
- {
- opcode: "playAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "play animation [ANAME] of [NAME], [TIMES] times",
- arguments: {
- TIMES: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "0",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "pauseAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [TOGGLE] animation [ANAME] of [NAME]",
- arguments: {
- TOGGLE: {
- type: Scratch.ArgumentType.NUMBER,
- menu: "pauseUn",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "stopAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "stop animation [ANAME] of [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- ],
- menus: {
- modelProperties: {
- acceptReporters: false,
- items: [{
- text: "Animations",
- value: "animations",
- }, ],
- },
- pauseUn: {
- acceptReporters: true,
- items: [{
- text: "Pause",
- value: "true",
- },
- {
- text: "Unpasue",
- value: "false",
- },
- ],
- },
- modelsList: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".glb"));
- if (models.length < 1) return [
- ["Load a model!"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- },
- };
- }
-
- async loadModelFile() {
- openFileExplorer(".glb").then((files) => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- {
- // From lily's assets
-
- // Thank you PenguinMod for providing this code.
- {
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- //const res = await Scratch.fetch(args.URL);
- //const buffer = await res.arrayBuffer();
- const buffer = arrayBuffer;
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Model loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading model.");
- }
- }
- // End of PenguinMod
- }
- };
-
- reader.readAsArrayBuffer(file);
- });
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME);
-
- createObject(args.NAME, group, args.GROUP);
- }
- getModel(args) {
- if (!models[args.NAME]) return;
- return Object.keys(models[args.NAME].actions).toString();
- }
-
- playAnimation(args) {
- const model = models[args.NAME];
- if (!model) {
- console.log("no model!");
- return;
- }
-
- const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
- if (!action) {
- console.log("no action!");
- return;
- }
-
- args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
-
- action.reset().play();
- }
- stopAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.stop();
- }
- pauseAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.paused = args.TOGGLE;
- }
- }
- Scratch.extensions.register(new ThreeGLB());
-
- class ThreeAddons {
- getInfo() {
- return {
- id: "threeAddons",
- name: "Three Addons",
- color1: "#c538a2ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- blockType: Scratch.BlockType.LABEL,
- text: "Orbit Control",
- },
- {
- opcode: "OrbitControl",
- blockType: Scratch.BlockType.COMMAND,
- text: "set addon Orbit Control [STATE]",
- arguments: {
- STATE: {
- type: Scratch.ArgumentType.STRING,
- menu: "onoff",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "Post Processing",
- },
- {
- opcode: "resetComposer",
- blockType: Scratch.BlockType.COMMAND,
- text: "reset composer",
- },
- {
- opcode: "bloom",
- blockType: Scratch.BlockType.COMMAND,
- text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- I: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.5,
- },
- T: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.5,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- },
- },
- {
- opcode: "godRays",
- blockType: Scratch.BlockType.COMMAND,
- text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- DEC: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.95,
- },
- DENS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- EXP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.1,
- },
- WEI: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.4,
- },
- RES: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- SAMP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 64,
- },
- },
- },
- {
- opcode: "dots",
- blockType: Scratch.BlockType.COMMAND,
- text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- A: {
- type: Scratch.ArgumentType.ANGLE,
- defaultValue: 0,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- },
- },
- {
- opcode: "depth",
- blockType: Scratch.BlockType.COMMAND,
- text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
- arguments: {
- FD: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 3,
- },
- FL: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.001,
- },
- BS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 4,
- },
- H: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 240,
- },
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "NORMAL",
- },
- },
- },
- "---",
- {
- opcode: "custom",
- blockType: Scratch.BlockType.COMMAND,
- text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myShader",
- },
- FRA: {
- type: Scratch.ArgumentType.STRING,
- },
- VER: {
- type: Scratch.ArgumentType.STRING,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "NORMAL",
- },
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- ],
- menus: {
- onoff: {
- acceptReporters: true,
- items: [{
- text: "enabled",
- value: "1",
- },
- {
- text: "disabled",
- value: "0",
- },
- ],
- },
- blendModes: {
- acceptReporters: false,
- items: [
- "SKIP",
- "SET",
- "ADD",
- "ALPHA",
- "AVERAGE",
- "COLOR",
- "COLOR_BURN",
- "COLOR_DODGE",
- "DARKEN",
- "DIFFERENCE",
- "DIVIDE",
- "DST",
- "EXCLUSION",
- "HARD_LIGHT",
- "HARD_MIX",
- "HUE",
- "INVERT",
- "INVERT_RGB",
- "LIGHTEN",
- "LINEAR_BURN",
- "LINEAR_DODGE",
- "LINEAR_LIGHT",
- "LUMINOSITY",
- "MULTIPLY",
- "NEGATION",
- "NORMAL",
- "OVERLAY",
- "PIN_LIGHT",
- "REFLECT",
- "SCREEN",
- "SRC",
- "SATURATION",
- "SOFT_LIGHT",
- "SUBTRACT",
- "VIVID_LIGHT",
- ],
- },
- },
- };
- }
-
- OrbitControl(args) {
- if (controls) controls.dispose();
-
- console.log("creating...", OrbitControls);
- controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
- controls.enableDamping = true;
-
- controls.enabled = !!args.STATE;
- console.log(controls);
- }
-
- resetComposer() {
- composer.passes = [];
- passes = {};
- customEffects = [];
- updateComposers();
- }
-
- bloom(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const bloomEffect = new BloomEffect({
- intensity: args.I,
- luminanceThreshold: args.T, // ← correct key
- luminanceSmoothing: args.S,
- blendFunction: BlendFunction[args.BLEND],
- });
- bloomEffect.blendMode.opacity.value = args.OP;
-
- const pass = new EffectPass(camera, bloomEffect);
-
- composer.addPass(pass);
- }
-
- godRays(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- let object = getObject(args.NAME);
- const sun = object;
-
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- });
- godRays.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, godRays);
- composer.addPass(pass);
- }
-
- dots(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const dot = new DotScreenEffect({
- angle: args.A,
- scale: args.S,
- blendFunction: BlendFunction[args.BLEND],
- });
- dot.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, dot);
- composer.addPass(pass);
- }
-
- depth(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- });
- dofEffect.blendMode.opacity.value = args.OP;
-
- const dofPass = new EffectPass(camera, dofEffect);
- composer.addPass(dofPass);
- }
-
- async custom(args) {
- function cleanGLSL(glslCode) {
- //delete multilines comments
- let cleanedCode = glslCode
- .replace(/\/\*[\s\S]*?\*\//g, " ")
- .replace(/ /g, "\n")
- .replace(/\/\/.*$/gm, " ")
- .replace(/; /g, ";\n");
-
- return cleanedCode;
- }
-
- let fs = cleanGLSL(`
- ${args.FRA}
- `);
- if (!args.FRA.trim()) {
- fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
- }
- const vs = cleanGLSL(`
- ${args.VER}
- `);
- console.log(fs);
- console.log(vs);
-
- const effect = new Effect("Custom", fs, {
- blendFunction: BlendFunction[args.BLEND],
- vertexShader: vs,
- uniforms: new Map([
- //uniforms usually in shaders... open to more!
- ["time", new THREE.Uniform(0.0)],
- [
- "resolution",
- new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
- ],
- ]),
- defines: new Map([
- ["USE_TIME", "1"],
- ["USE_VERTEX_TRANSFORM", ""],
- ]),
- });
-
- effect.blendMode.opacity.value = args.OP;
-
- const pass = new EffectPass(camera, effect);
- composer.addPass(pass);
-
- customEffects.push(effect);
- }
- }
- Scratch.extensions.register(new ThreeAddons());
-
- class RapierPhysics {
- getInfo() {
- return {
- id: "rapierPhysics",
- name: "RAPIER Physics",
- color1: "#222222",
- color2: "#203024ff",
- color3: "#78f07eff",
- blocks: [{
- opcode: "createWorld",
- blockType: Scratch.BlockType.COMMAND,
- text: "create world | gravity:[G]",
- arguments: {
- G: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,-9.81,0]",
- },
- },
- },
- {
- opcode: "getWorld",
- blockType: Scratch.BlockType.REPORTER,
- text: "get world [PROPERTY]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "wProp",
- },
- },
- },
- "---",
- {
- opcode: "objectPhysics",
- blockType: Scratch.BlockType.COMMAND,
- text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
- arguments: {
- state2: {
- type: Scratch.ArgumentType.STRING,
- menu: "state2",
- },
- state: {
- type: Scratch.ArgumentType.STRING,
- menu: "state",
- defaultValue: "true",
- },
- type: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectTypes",
- defaultValue: "dynamic",
- },
- collider: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderTypes",
- defaultValue: "cuboid",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- mass: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- density: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- friction: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "0.5",
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.LABEL,
- text: "- RigidBody",
- },
- {
- opcode: "setRB",
- blockType: Scratch.BlockType.COMMAND,
- text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "rigidBodySets",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "getRB",
- blockType: Scratch.BlockType.REPORTER,
- text: "get rigidbody [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "rigidBodyProperties",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "lockObjectAxis",
- blockType: Scratch.BlockType.COMMAND,
- text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
- arguments: {
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "lockAxes",
- },
- X: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- Y: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- Z: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- },
- },
- "---",
- {
- opcode: "addForce",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,10,0]",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "forces",
- defaultValue: "addForce",
- },
- SPACE: {
- type: Scratch.ArgumentType.STRING,
- menu: "spaces",
- defaultValue: "world",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "resetForces",
- blockType: Scratch.BlockType.COMMAND,
- text: "reset [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "resetF",
- defaultValue: "resetForces",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "enableCCD",
- blockType: Scratch.BlockType.COMMAND,
- text: "enable Continuous Collision Detection for [OBJECT] [state]",
- arguments: {
- state: {
- type: Scratch.ArgumentType.STRING,
- menu: "state",
- defaultValue: "true",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "oPropS",
- defaultValue: "physics",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "fixedJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- RA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- RB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- {
- opcode: "sphericalJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- },
- },
- {
- opcode: "revoluteJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- X: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.LABEL,
- text: "- Collider",
- },
- {
- opcode: "setC",
- blockType: Scratch.BlockType.COMMAND,
- text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderSets",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "getC",
- blockType: Scratch.BlockType.REPORTER,
- text: "get collider [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderProperties",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "sensorSingle",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is sensor [SENSOR] touching [OBJECT]?",
- arguments: {
- SENSOR: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySensor",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "sensorAll",
- blockType: Scratch.BlockType.REPORTER,
- text: "objects touching sensor [SENSOR]",
- arguments: {
- SENSOR: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySensor",
- },
- },
- },
- ],
- menus: {
- wProp: {
- acceptReporters: false,
- items: [{
- text: "Gravity",
- value: "gravity",
- },
- {
- text: "log to console",
- value: "log",
- },
- ],
- },
- tf: {
- acceptReporters: true,
- items: [{
- text: "false",
- value: "false",
- },
- {
- text: "true",
- value: "true",
- },
- ],
- },
- lockAxes: {
- acceptReporters: false,
- items: [{
- text: "Translation",
- value: "setEnabledTranslations",
- },
- {
- text: "Rotation",
- value: "setEnabledRotations",
- },
- ],
- },
- rigidBodyProperties: {
- acceptReporters: false,
- items: [{
- text: "Type",
- value: "bodyType",
- },
- {
- text: "Linear Velocity",
- value: "linvel",
- },
- {
- text: "Angular Velocity",
- value: "angvel",
- },
- {
- text: "Translation (position)",
- value: "translation",
- },
- {
- text: "Rotation (quaternion)",
- value: "rotation",
- },
- {
- text: "Mass",
- value: "mass",
- },
- //{text: "Center of Mass", value: "centerOfMass"},
- {
- text: "Linear Damping",
- value: "linearDamping",
- },
- {
- text: "Angular Damping",
- value: "angularDamping",
- },
- {
- text: "Is Sleeping?",
- value: "isSleeping",
- },
- //{text: "Can Sleep?", value: "isCanSleep"},
- {
- text: "Gravity Scale",
- value: "gravityScale",
- },
- {
- text: "Is Fixed?",
- value: "isFixed",
- },
- {
- text: "Is Dynamic?",
- value: "isDynamic",
- },
- {
- text: "Is Kinematic?",
- value: "isKinematic",
- },
- //{text: "Sleeping", value: "sleeping"}
- ],
- },
- rigidBodySets: {
- acceptReporters: false,
- items: [
- //{text: "Linear Velocity", value: "setLinvel"},
- //{text: "Angular Velocity", value: "setAngvel"},
- //{text: "Mass", value: "setMass"},
- {
- text: "Gravity Scale",
- value: "setGravityScale",
- },
- //{text: "Can Sleep?", value: "setCanSleep"},
- //{text: "Sleeping", value: "sleeping"},
- {
- text: "Linear Damping",
- value: "setLinearDamping",
- },
- {
- text: "Angular Damping",
- value: "setAngularDamping",
- },
- {
- text: "Is Fixed?",
- value: "isFixed",
- },
- {
- text: "Is Dynamic?",
- value: "isDynamic",
- },
- {
- text: "Is Kinematic?",
- value: "isKinematic",
- },
- ],
- },
- colliderProperties: {
- acceptReporters: false,
- items: [
- //{text: "Collider Type", value: "type"},
- {
- text: "Is Sensor?",
- value: "isSensor",
- },
- {
- text: "Friction",
- value: "friction",
- },
- {
- text: "Restitution",
- value: "restitution",
- },
- {
- text: "Density",
- value: "density",
- },
- {
- text: "Mass",
- value: "mass",
- },
- {
- text: "Position",
- value: "translation",
- },
- {
- text: "Rotation",
- value: "rotation",
- },
- //{text: "Area", value: "area"},
- {
- text: "Volume",
- value: "volume",
- },
- {
- text: "Collision Groups",
- value: "collisionGroups",
- },
- //{text: "Collision Mask", value: "collisionMask"},
- //{text: "Is Enabled?", value: "enabled"},
- //{text: "Contact Count", value: "contactCount"},
- //{text: "RigidBody Handle", value: "rigidBody"}
- ],
- },
- colliderSets: {
- acceptReporters: false,
- items: [{
- text: "Friction",
- value: "setFriction",
- },
- {
- text: "Restitution",
- value: "setRestitution",
- },
- {
- text: "Density",
- value: "setDensity",
- },
- {
- text: "Is Sensor?",
- value: "setSensor",
- },
- {
- text: "Collision Groups",
- value: "setCollisionGroups",
- },
- //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
- //{text: "Position Offset", value: "setTranslation"},
- //{text: "Rotation Offset", value: "setRotation"}
- ],
- },
- state: {
- acceptReporters: true,
- items: [{
- text: "on",
- value: "true",
- },
- {
- text: "off",
- value: "false",
- },
- ],
- },
- state2: {
- acceptReporters: true,
- items: [{
- text: "false",
- value: "false",
- },
- {
- text: "true (must be fixed)",
- value: "true",
- },
- ],
- },
- spaces: {
- acceptReporters: false,
- items: [{
- text: "World",
- value: "world",
- },
- {
- text: "Local",
- value: "local",
- },
- ],
- },
- objectTypes: {
- acceptReporters: false,
- items: [{
- text: "Dynamic",
- value: "dynamic",
- },
- {
- text: "Fixed",
- value: "fixed",
- },
- {
- text: "Kinematic Position Based",
- value: "kinematicPositionBased",
- },
- ],
- },
- colliderTypes: {
- acceptReporters: false,
- items: [{
- text: "Box, Rectangle, cuboid",
- value: "cuboid",
- },
- {
- text: "Sphere, ball",
- value: "ball",
- },
- {
- text: "Custom, complex simple shapes, convexHull",
- value: "convexHull",
- },
- {
- text: "Precision, TriMesh",
- value: "trimesh",
- },
- ],
- },
- forces: {
- acceptReporters: false,
- items: [{
- text: "Force",
- value: "addForce",
- },
- {
- text: "Torque (rotation)",
- value: "addTorque",
- },
- {
- text: "Apply Impulse",
- value: "applyImpulse",
- },
- {
- text: "Apply Torque Impulse (rotation)",
- value: "applyTorqueImpulse",
- },
- {
- text: "Linear Velocity",
- value: "setLinvel",
- },
- {
- text: "Angular Velocity",
- value: "setAngvel",
- },
- ],
- },
- resetF: {
- acceptReporters: false,
- items: [{
- text: "Forces",
- value: "resetForces",
- },
- {
- text: "Torques",
- value: "resetTorques",
- },
- ],
- },
- },
- };
- }
- joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
- }
-
- fixedJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- let RA = JSON.parse(args.RA).map(Number);
- let RB = JSON.parse(args.RB).map(Number);
-
- RA = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RA[0]),
- THREE.MathUtils.degToRad(RA[1]),
- THREE.MathUtils.degToRad(RA[2])
- )
- );
- RB = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RB[0]),
- THREE.MathUtils.degToRad(RB[1]),
- THREE.MathUtils.degToRad(RB[2])
- )
- );
-
- const data = RAPIER.JointData.fixed({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- },
- RA, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- },
- RB
- );
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- sphericalJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
-
- const data = RAPIER.JointData.spherical({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- revoluteJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- const x = JSON.parse(args.X).map(Number);
-
- const data = RAPIER.JointData.revolute({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- }, {
- x: x[0],
- y: x[1],
- z: x[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- prismaticJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- const x = JSON.parse(args.X).map(Number);
-
- const data = RAPIER.JointData.prismatic({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- }, {
- x: x[0],
- y: x[1],
- z: x[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- createWorld(args) {
- const v3 = JSON.parse(args.G).map(Number);
- const gravity = {
- x: v3[0],
- y: v3[1],
- z: v3[2],
- };
- physicsWorld = new RAPIER.World(gravity);
-
- console.log(physicsWorld);
- }
-
- getWorld(args) {
- if (args.PROPERTY === "log") {
- console.log(physicsWorld);
- return "logged";
- }
- return JSON.stringify(physicsWorld[args.PROPERTY]);
- }
-
- setRB(args) {
- let value = args.VALUE;
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
- let object = getObject(args.OBJECT);
- object.rigidBody[args.PROPERTY](value);
- }
- setC(args) {
- let value = args.VALUE;
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
- let object = getObject(args.OBJECT);
- object.collider[args.PROPERTY](value);
- }
-
- getRB(args) {
- let object = getObject(args.OBJECT);
- return JSON.stringify(object.rigidBody[args.PROPERTY]());
- }
- getC(args) {
- let object = getObject(args.OBJECT);
- return JSON.stringify(object.collider[args.PROPERTY]());
- }
-
- lockObjectAxis(args) {
- let object = getObject(args.OBJECT);
- const x = !JSON.parse(args.X);
- const y = !JSON.parse(args.Y);
- const z = !JSON.parse(args.Z);
- object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
- }
-
- objectPhysics(args) {
- let object = getObject(args.OBJECT);
- object.physics = JSON.parse(args.state);
-
- if (JSON.parse(args.state)) {
- //if already exists delete:
- if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody);
- object.rigidBody = null;
- object.collider = null;
- }
- /*asing a rigidbody and collider to object and add them to physicsWorld*/
- let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
- .setTranslation(object.position.x, object.position.y, object.position.z)
- .setRotation({
- w: object.quaternion._w,
- x: object.quaternion._x,
- y: object.quaternion._y,
- z: object.quaternion._z,
- });
-
- let colliderDesc;
- switch (args.collider) {
- case "cuboid":
- colliderDesc = createCuboidCollider(object);
- break;
- case "ball":
- colliderDesc = createBallCollider(object);
- break;
- case "convexHull":
- colliderDesc = createConvexHullCollider(object);
- break;
- case "trimesh":
- colliderDesc = TriMesh(object);
- break;
- }
- colliderDesc
- .setSensor(JSON.parse(args.state2))
- .setMass(args.mass)
- .setDensity(args.density)
- .setFriction(args.friction);
-
- let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
- let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
-
- object.rigidBody = rigidBody;
- object.collider = collider;
- } else {
- /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody);
- object.rigidBody = null;
- object.collider = null;
- }
- }
-
- enableCCD(args) {
- let object = getObject(args.OBJECT);
- if (object.physics) {
- let rigidBody = object.rigidBody;
- rigidBody.enableCcd(JSON.parse(args.state));
- }
- }
-
- addForce(args) {
- let object = getObject(args.OBJECT);
- const vector = JSON.parse(args.VALUE).map(Number);
-
- let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
- if (args.SPACE === "local") {
- force.applyQuaternion(object.quaternion);
- }
-
- object.rigidBody[args.PROPERTY](force, true);
- }
-
- resetForces(args) {
- rigidBody[args.PROPERTY](true);
- }
-
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR);
-
- let object = getObject(args.OBJECT);
-
- let touching = false;
- physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
- if (otherCollider === object.collider) touching = true;
- });
-
- return touching;
- }
-
- sensorAll(args) {
- const sensor = getObject(args.SENSOR);
-
- const touchedObjects = [];
-
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
- // find owner of collider
- const otherObject = scene.children.find((o) => o.collider === otherCollider);
- console.log(otherCollider);
- if (otherObject) touchedObjects.push(otherObject.name);
- });
-
- return JSON.stringify(touchedObjects);
- }
- }
- Scratch.extensions.register(new RapierPhysics());
-
- //Thanks to the PointerLock extension of Turbowarp
- const mouse = vm.runtime.ioDevices.mouse;
- let isLocked = false;
- let isPointerLockEnabled = false;
-
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
-
- const postMouseData = (e, isDown) => {
- const {
- movementX,
- movementY
- } = e;
- const {
- width,
- height
- } = rect;
- const x = mouse._clientX + movementX;
- const y = mouse._clientY - movementY;
- mouse._clientX = x;
- mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
- mouse._clientY = y;
- mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
- if (typeof isDown === "boolean") {
- const data = {
- button: e.button,
- isDown,
- };
- originalPostIOData(data);
- }
- };
-
- const mouseDevice = vm.runtime.ioDevices.mouse;
- const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
- mouseDevice.postData = (data) => {
- if (!isPointerLockEnabled) {
- return originalPostIOData(data);
- }
- };
-
- document.addEventListener(
- "mousedown",
- (e) => {
- // @ts-expect-error
- if (threeRenderer.domElement.contains(e.target)) {
- if (isLocked) {
- postMouseData(e, true);
- } else if (isPointerLockEnabled) {
- threeRenderer.domElement.requestPointerLock();
- }
- }
- },
- true
- );
- document.addEventListener(
- "mouseup",
- (e) => {
- if (isLocked) {
- postMouseData(e, false);
- // @ts-expect-error
- } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
- threeRenderer.domElement.requestPointerLock();
- }
- },
- true
- );
- document.addEventListener(
- "mousemove",
- (e) => {
- if (isLocked) {
- postMouseData(e);
- }
- },
- true
- );
-
- document.addEventListener("pointerlockchange", () => {
- isLocked = document.pointerLockElement === threeRenderer.domElement;
- });
- document.addEventListener("pointerlockerror", (e) => {
- console.error("Pointer lock error", e);
- });
-
- const oldStep = vm.runtime._step;
- vm.runtime._step = function(...args) {
- const ret = oldStep.call(this, ...args);
- if (isPointerLockEnabled) {
- const {
- width,
- height
- } = rect;
- mouse._clientX = width / 2;
- mouse._clientY = height / 2;
- mouse._scratchX = 0;
- mouse._scratchY = 0;
- }
- return ret;
- };
-
- vm.runtime.on("PROJECT_LOADED", () => {
- isPointerLockEnabled = false;
- if (isLocked) {
- document.exitPointerLock();
- }
- });
-
- class Pointerlock {
- getInfo() {
- return {
- id: "threepointerlockmod",
- name: "Pointerlock for Extra 3D",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "setLocked",
- blockType: Scratch.BlockType.COMMAND,
- text: "set pointer lock [enabled]",
- arguments: {
- enabled: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "true",
- menu: "enabled",
- },
- },
- },
- {
- opcode: "isLocked",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "pointer locked?",
- },
- ],
- menus: {
- enabled: {
- acceptReporters: true,
- items: [{
- text: "enabled",
- value: "true",
- },
- {
- text: "disabled",
- value: "false",
- },
- ],
- },
- },
- };
- }
-
- setLocked(args) {
- isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
- if (!isPointerLockEnabled && isLocked) {
- document.exitPointerLock();
- }
- }
-
- isLocked() {
- return isLocked;
- }
- }
- Scratch.extensions.register(new Pointerlock());
- });
-})(Scratch);
diff --git a/threejsD_BASE_13852.js b/threejsD_BASE_13852.js
deleted file mode 100644
index ced1928..0000000
--- a/threejsD_BASE_13852.js
+++ /dev/null
@@ -1,2413 +0,0 @@
-// Name: Extra 3D
-// ID: threejsExtension
-// Description: Use three js inside Turbowarp! A 3D graphics library.
-// By: Civero
-// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
-
-(function (Scratch) {
- "use strict";
-
- if (!Scratch.extensions.unsandboxed) {
- throw new Error("Three-D extension must run unsandboxed");
- }
-
- if (Scratch.vm.runtime.isPackaged) {alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`); return}
- //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
-
- const vm = Scratch.vm;
- const runtime = vm.runtime
- const renderer = Scratch.renderer;
- const canvas = renderer.canvas
- const Cast = Scratch.Cast;
- const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
-
- let alerts = false
- console.log("alerts are "+ (alerts ? "enabled" : "disabled"))
-
- let isMouseDown = { left: false, middle: false, right: false }
- let prevMouse = { left: false, middle: false, right: false }
-
- let lastWidth = 0
- let lastHeight = 0
-
- let THREE
- let clock
- let running
- let loopId
- //Addons
- let GLTFLoader
- let gltf
- let OrbitControls
- let controls
- let BufferGeometryUtils
- let TextGeometry
- let fontLoad
- //Physics
- let RAPIER
- let physicsWorld
-
- let threeRenderer
- let scene
- let camera
- let eulerOrder = "YXZ"
-
- let composer
- let passes = {}
- let customEffects = []
- let renderTargets = {}
-
- let materials = {}
- let geometries = {}
- let lights = {}
- let models = {}
-
- let assets = { //should i place materials, geometries; inside too?
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {}, //not the same as the global one! this one only stores textures
- }
-
- let raycastResult = []
-
- function resetor(level) {
- camera = undefined
- composer.reset()
-
- passes = {}
- customEffects = []
- renderTargets = {}
-
- materials = {}
- geometries = {}
- lights = {}
- models = {}
-
- if (level > 0) {
- assets = {
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {},
- }
- }
-
- updateComposers()
- }
-
-//utility
- function vector3ToString(prop) {
- if (!prop) return "0,0,0";
-
- const x = (typeof(prop.x) === "number") ? prop.x : (typeof(prop._x) === "number") ? prop._x : (JSON.stringify(prop).includes("X")) ? prop : 0
- const y = (typeof(prop.y) === "number") ? prop.y : (typeof(prop.y) === "number") ? prop._y : 0
- const z = (typeof(prop.z) === "number") ? prop.z : (typeof(prop.z) === "number") ? prop.z : 0
-
- return [x, y, z]
- }
-
-//objects
- function createObject(name, content, parentName) {
- let object = getObject(name, true)
- if (object) {
- removeObject(name)
- alerts ? alert(name + " already exsisted, will replace!") : null
- }
- content.name = name
- content.rotation._order = eulerOrder
- parentName === scene.name ? object = scene : object = getObject(parentName)
- content.physics = false
-
- object.add(content)
- }
- function removeObject(name) {
- let object = getObject(name)
- if (!object) return
-
- scene.remove(object)
-
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true)
- physicsWorld.removeRigidBody(object.rigidBody, true)
- object.rigidBody = null
- object.collider = null
- }
- if (object.isLight) {
- delete(lights[name])
- }
- }
- function getObject(name, isNew) {
- let object = null
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null; return;}
- object = scene.getObjectByName(name)
- if (!object && !isNew) {alerts ? alert(name + " does not exist! Add it to scene"):null; return;}
- return object
- }
-
-//materials
- function encodeCostume (name) {
- if (name.startsWith("data:image/")) return name
- return Scratch.vm.editingTarget.sprite.costumes.find(c => c.name === name).asset.encodeDataURI()
- }
- function setTexutre (texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace
-
- if (mode === "Pixelate") {
- texture.minFilter = THREE.NearestFilter;
- texture.magFilter = THREE.NearestFilter;
- } else { //Blur
- texture.minFilter = THREE.NearestMipmapLinearFilter
- texture.magFilter = THREE.NearestMipmapLinearFilter
- }
-
- if (style === "Repeat") {
- texture.wrapS = THREE.RepeatWrapping
- texture.wrapT = THREE.RepeatWrapping
- texture.repeat.set(x, y)
- }
-
- texture.generateMipmaps = true;
- }
- async function resizeImageToSquare(uri, size = 256) {
- return new Promise((resolve) => {
- const img = new Image()
- img.onload = () => {
- const canvas = document.createElement('canvas')
- canvas.width = size
- canvas.height = size
- const ctx = canvas.getContext('2d')
-
- // clear + draw image scaled to fit canvas
- ctx.clearRect(0, 0, size, size)
- ctx.drawImage(img, 0, 0, size, size)
-
- resolve(canvas.toDataURL()) // return normalized Data URI
- //delete canvas?
- };
- img.src = uri
- });
-}
-//light
-function updateShadowFrustum(light, focusPos) {
- if (light.type !== "DirectionalLight") return
-
- // Frustum Size - Increase this value to cover a larger area.
- const d = 50;
-
- // Update Orthographic Shadow Camera Frustum
- const shadowCamera = light.shadow.camera;
-
- // Set the width/height of the frustum
- shadowCamera.left = -d;
- shadowCamera.right = d;
- shadowCamera.top = d;
- shadowCamera.bottom = -d;
-
- // Determine ranges
- shadowCamera.near = 0.1
- shadowCamera.far = 500
-
- // Position the Light and its Target
- light.target.position.copy(focusPos);
- const direction = light.position.clone().sub(light.target.position).normalize();
- light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
-
- // Ensure matrices are updated.
- light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix()
- light.shadow.needsUpdate = true;
-}
-//composer
-function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
-
- // always recreate the RenderPass to point to the current scene/camera
- passes["Render"] = new RenderPass(scene, camera);
-
- // ensure composer has a RenderPass as the first pass
- const hasRender = composer.passes.some(p => p && p.scene);
- if (!hasRender) composer.addPass(passes["Render"]);
- else {
- // if composer already has one, replace it so it references current scene/camera
- const idx = composer.passes.findIndex(p => p && p.scene);
- composer.passes[idx] = passes["Render"];
- }
-}
-//utility
-function getMouseNDC(event) {
- // Use threeRenderer.domElement for correct offset
- const rect = threeRenderer.domElement.getBoundingClientRect();
- const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
- const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
- return [x, y];
-}
-function checkCanvasSize() {
- const { width, height } = canvas
- if (width !== lastWidth || height !== lastHeight) {
- lastWidth = width
- lastHeight = height
- resize()
- }
- requestAnimationFrame(checkCanvasSize) //rerun next frame
-}
-//physics
-function computeWorldBoundingBox(mesh) {
- // Create a Box3 in world coordinates
- const box = new THREE.Box3().setFromObject(mesh);
- const size = new THREE.Vector3();
- box.getSize(size);
- const center = new THREE.Vector3();
- box.getCenter(center);
- return { size, center };
-}
-function createCuboidCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
- const collider = RAPIER.ColliderDesc.cuboid(
- size.x / 2,
- size.y / 2,
- size.z / 2
- )
- return collider;
-}
-function createBallCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
- // radius = 1/2 of the largest verticie
- const radius = Math.max(size.x, size.y, size.z) / 2;
- const collider = RAPIER.ColliderDesc.ball(radius)
- return collider //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
-}
-function createConvexHullCollider(mesh) {
- mesh.updateWorldMatrix(true, false);
-
- const position = mesh.geometry.attributes.position;
- const vertices = [];
- const vertex = new THREE.Vector3();
-
- // Matrix for scale only
- const scaleMatrix = new THREE.Matrix4().makeScale(
- mesh.scale.x,
- mesh.scale.y,
- mesh.scale.z
- );
-
- for (let i = 0; i < position.count; i++) {
- vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
- vertices.push(vertex.x, vertex.y, vertex.z);
- }
-
- const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
- return collider;
-}
-function TriMesh(mesh) {
- // Get the positions array (from your geoPoints function)
-const positions = mesh.geometry.attributes.position.array;
-const numVertices = positions.length / 3;
-
-// Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
-const indices = Array.from({ length: numVertices }, (_, i) => i);
-
-const collider = RAPIER.ColliderDesc.trimesh(
- positions,
- new Uint32Array(indices)
-);
-
-return collider
-}
-function getModel(model, name) {
- const file = runtime.getTargetForStage().getSounds().find(c => c.name === model)
- if (!file) return
-
-return new Promise((resolve, reject) => {
- gltf.parse(
- file.asset.data.buffer,
- "",
- gltf => {
- const root = gltf.scene
- root.traverse(child => {
- if (child.isMesh) {
- child.castShadow = true
- child.receiveShadow = true
- }
- });
-
- const mixer = new THREE.AnimationMixer(root)
- const actions = {}
- gltf.animations.forEach(clip => {
- const act = mixer.clipAction(clip)
- act.clampWhenFinished = true
- actions[clip.name] = act
- });
-
- models[name] = { root, mixer, actions }
- resolve(root)
- },
- error => {
- console.error("Error parsing GLB model:", error)
- reject(error)
- }
- )})
-}
-async function openFileExplorer(format) {
- return new Promise((resolve) => {
- const input = document.createElement("input");
- input.type = "file"
- input.accept = format
- input.multiple = false
- input.onchange = () => {
- resolve(input.files)
- input.remove()
- };
- input.click();
- })
-}
-function getMeshesUsingTexture(scene, targetTexture) {
- const meshes = []
-
- scene.traverse(object => {
- if (object.material) {
- const materials = Array.isArray(object.material) ? object.material : [object.material]
- for (const material of materials) {
- if (material.map === targetTexture) {
- meshes.push(object)
- break
- }
- }
- }
- })
-
- return meshes
-}
-function getAsset(path) {
- if (typeof(path) == "string") { //string?
- if (path.includes("/")) { //has the /?
- const value = path.split("/")
- console.log(value[0], value[1])
- return assets[value[0]][value[1]]
- }
- }
-
- return JSON.parse(path) //boolean or number
-}
-
-let mouseNDC = [0, 0]
-//loops/init
-function stopLoop() {
- if (!running) return
- running = false
-
- if (loopId) {
- cancelAnimationFrame(loopId)
- loopId = null
- if (threeRenderer) threeRenderer.clear();
- }
-}
-async function load() {
- if (!THREE) {
-
- // @ts-ignore
- THREE = await import("https://esm.sh/three@0.180.0")
- //Addons
- GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js")
- OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js")
- BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js")
- TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js")
- const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js")
- fontLoad = new FontLoader.FontLoader()
-
- const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8")
- const {
- EffectComposer,
- EffectPass,
- RenderPass,
-
- Effect,
- BloomEffect,
- GodRaysEffect,
- DotScreenEffect,
- DepthOfFieldEffect,
-
- BlendFunction
- } = POSTPROCESSING
- //so i can use them later as global
- window.EffectComposer = EffectComposer;
- window.EffectPass = EffectPass;
- window.RenderPass = RenderPass;
- window.Effect = Effect;
- window.BloomEffect = BloomEffect;
- window.GodRaysEffect = GodRaysEffect;
- window.DotScreenEffect = DotScreenEffect;
- window.DepthOfFieldEffect = DepthOfFieldEffect;
- window.BlendFunction = BlendFunction;
-
- RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0")
- await RAPIER.init()
-
- threeRenderer = new THREE.WebGLRenderer({
- powerPreference: "high-performance",
- antialias: false,
- stencil: false,
- depth: true
- })
- threeRenderer.setPixelRatio(window.devicePixelRatio)
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping // HDR look (test)
- //threeRenderer.toneMappingExposure = 1.0 //(test)
-
- threeRenderer.shadowMap.enabled = true
- threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap // (optional)
- threeRenderer.domElement.style.pointerEvents = 'auto' //will disable turbowarp mouse events, but enable threejs's
-
- gltf = new GLTFLoader.GLTFLoader()
- clock = new THREE.Clock()
-
- // Example: create a composer
- composer = new EffectComposer(threeRenderer, {frameBufferType: THREE.HalfFloatType})
-
- renderer.addOverlay( threeRenderer.domElement, "manual" )
- renderer.addOverlay(canvas, "manual")
- renderer.setBackgroundColor(1, 1, 1, 0)
-
- resize()
-
- window.addEventListener("mousedown", e => {
- if (e.button === 0) isMouseDown.left = true
- if (e.button === 1) isMouseDown.middle = true
- if (e.button === 2) isMouseDown.right = true
- })
- window.addEventListener("mouseup", e => {
- if (e.button === 0) isMouseDown.left = false; prevMouse.left = false
- if (e.button === 1) isMouseDown.middle = false; prevMouse.middle = false
- if (e.button === 2) isMouseDown.right = false; prevMouse.right = false
- })
- // prevent contextmenu on right click
- threeRenderer.domElement.addEventListener("contextmenu", e => e.preventDefault());
-
- threeRenderer.domElement.addEventListener('mousemove', (event) => {
- mouseNDC = getMouseNDC(event);
- })
-
- running = false
- load()
-
- startRenderLoop()
- runtime.on('PROJECT_START', () => startRenderLoop())
- runtime.on('PROJECT_STOP_ALL', () => stopLoop())
- runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
- //if (!runtime.isPackaged) checkCanvasSize() //only in editor
- }
- }
-function startRenderLoop() {
- if (running) return
- running = true
-
- const loop = () => {
- if (!running) return
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step()
-
- scene.children.forEach(obj => {
- if (!(obj.isMesh) || !(obj.physics)) return
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation())
- obj.quaternion.copy(obj.rigidBody.rotation())
- }
- })
-
- }
- if (scene && camera) {
- if (controls) controls.update()
-
- const delta = clock.getDelta()
- Object.values(models).forEach( model => { if (model) model.mixer.update(delta) } )
-
- Object.values(lights).forEach(light => updateShadowFrustum(light, camera.position))
-
- //update custom effects time
- customEffects.forEach(e => {
- if (e.uniforms.get('time')) {
- e.uniforms.get('time').value += delta
- }
- })
- Object.values(renderTargets).forEach(t => {
- if ( t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height
- t.camera.updateProjectionMatrix()
- }
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
-
- displayMeshes.forEach(mesh => {
- mesh.visible = false
- })
-
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target)
- threeRenderer.clear(true, true, true)
- threeRenderer.render(scene, t.camera)
- } else {
- t.target.clear(threeRenderer)
- t.camera.update( threeRenderer, scene ) //cubeCamera
- }
-
- displayMeshes.forEach(mesh => {
- mesh.visible = true
- })
- })
-
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height
- camera.updateProjectionMatrix()
- threeRenderer.setRenderTarget(null)
- composer.render(delta)
- }
-
- loopId = requestAnimationFrame(loop)
- }
-
- loopId = requestAnimationFrame(loop)
-}
-
-function resize() {
- const w = canvas.width
- const h = canvas.height
-
- threeRenderer.setSize(w, h)
- composer.setSize(w, h)
- customEffects.forEach(e => {
- if (e.uniforms.get('resolution')) {
- e.uniforms.get('resolution').value.set(w,h)
- }
- })
-
- if (camera) {
- camera.aspect = w / h
- camera.updateProjectionMatrix()
-}
-}
-//wait until all packages are loaded
-Promise.resolve(load()).then(() => {
-
- class threejsExtension {
- getInfo() {
- return {
- id: "threejsExtension",
- name: "Extra 3D",
- color1: "#222222",
- color2: "#222222",
- color3: "#11cc99",
- menuIconURI,
- blockIconURI: menuIconURI,
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Show Docs", func: "openDocs"},
- {blockType: Scratch.BlockType.BUTTON, text: "Toggle Alerts", func: "alerts"},
- ],
- menus: {}
- }}
- openDocs(){
- open("https://civ3ro.github.io/extensions/Documentation/")
- }
- alerts() {alerts = !alerts; alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!")}
- }
- Scratch.extensions.register(new threejsExtension())
-
- class ThreeRenderer {
- getInfo() {
- return {
- id: "threeRenderer",
- name: "Three Renderer",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setRendererRatio", blockType: Scratch.BlockType.COMMAND, text: "set Pixel Ratio to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"}}},
- {opcode: "eulerOrder", blockType: Scratch.BlockType.COMMAND, text: "set euler order to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "YXZ"}}},
- ],
- menus: {}
- }}
-
- setRendererRatio(args) {
- threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE)
- }
- eulerOrder(args) {
- eulerOrder = args.VALUE
- console.log("euler order set to", eulerOrder)
- }
-
- }
- Scratch.extensions.register(new ThreeRenderer())
-
- class ThreeScene {
- constructor() {
- this.THREE = THREE;
- this.scenes = {};
- }
-
- getInfo() {
- return {
- id: "threeScene",
- name: "Three Scene",
- color1: "#4638c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newScene", blockType: Scratch.BlockType.COMMAND, text: "new Scene [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}}},
-
- {opcode: "setSceneProperty", blockType: Scratch.BlockType.COMMAND, text: "set Scene [PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "sceneProperties", defaultValue: "background"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
- "---",
- {opcode: "getSceneObjects", blockType: Scratch.BlockType.REPORTER, text: "get Scene [THING]", arguments:{THING: {type: Scratch.ArgumentType.STRING, menu: "sceneThings"}}},
- {opcode: "reset", blockType: Scratch.BlockType.COMMAND, text: "Reset Everything"}
- ],
- menus: {
- sceneProperties: {acceptReporters: false, items: [
- {text: "Background", value: "background"},{text: "Background Blurriness", value: "backgroundBlurriness"},{text: "Background Intensity", value: "backgroundIntensity"},{text: "Background Rotation", value: "backgroundRotation"},
- {text: "Environment", value: "environment"},{text: "Environment Intensity", value: "environmentIntensity"},{text: "Environment Rotation", value: "environmentRotation"},{text: "Fog", value: "fog"},
- ]},
- sceneThings: {acceptReporters: false, items: ["Objects", "Materials", "Geometries","Lights","Scene Properties","Other assets"]},
-
- }
- }}
-
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME
- scene.background = new THREE.Color("#222")
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {...this.scenes, {scene}};
- resetor(0)
- }
-
- reset() {
- resetor(1)
- }
-
- async setSceneProperty(args) {
- const property = args.PROPERTY;
- const value = getAsset(args.VALUE);
-
- scene[property] = value;
- }
- getSceneObjects(args){
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse(obj => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- }
- else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials))
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries))
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights))
- else if (args.THING === "Scene Properties") {console.log(scene); return "check console"}
- else if (args.THING === "Other assets") return JSON.stringify(assets)
-
- return JSON.stringify(names); // if objects
- }
-
- }
- Scratch.extensions.register(new ThreeScene())
-
- class ThreeCameras {
- getInfo() {
- return {
- id: "threeCameras",
- name: "Three Cameras",
- color1: "#38c59bff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addCamera", blockType: Scratch.BlockType.COMMAND, text: "add camera [TYPE] [CAMERA] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "cameraTypes"}}},
- {opcode: "setCamera", blockType: Scratch.BlockType.COMMAND, text: "set camera [PROPERTY] of [CAMERA] to [VALUE]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "0.1", exemptFromNormalization: true}}},
- {opcode: "getCamera", blockType: Scratch.BlockType.REPORTER, text: "get camera [PROPERTY] of [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}}},
- "---",
- {opcode: "renderSceneCamera", blockType: Scratch.BlockType.COMMAND, text: "set rendering camera to [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}}},
- "---",
- {opcode: "cubeCamera", blockType: Scratch.BlockType.COMMAND, text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "cubeCamera"}, GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
- "---",
- {opcode: "renderTarget", blockType: Scratch.BlockType.COMMAND, text: "set a RenderTarget: [RT] for camera [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
- {opcode: "sizeTarget", blockType: Scratch.BlockType.COMMAND, text: "set RenderTarget [RT] size to [W] [H]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, W: {type: Scratch.ArgumentType.NUMBER, defaultValue: 480}, H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 360},} },
- {opcode: "getTarget", blockType: Scratch.BlockType.REPORTER, text: "get RenderTarget: [RT] texture", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
- {opcode: "removeTarget", blockType: Scratch.BlockType.COMMAND, text: "remove RenderTarget: [RT]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
- ],
- menus: {
- cameraTypes: {acceptReporters: false, items: [
- {text: "Perspective", value: "PerspectiveCamera"},
- ]},
- cameraProperties: {acceptReporters: false, items: [
- {text: "Near", value: "near"},{text: "Far", value: "far"},{text: "FOV", value: "fov"},{text: "Focus (nothing...)", value: "focus"},{text: "Zoom", value: "zoom"},
- ]},
- }
- }}
- addCamera(args) {
- let v2 = new THREE.Vector2()
- threeRenderer.getSize(v2)
- const object = new THREE.PerspectiveCamera(90, v2.x / v2.y )
- object.position.z = 3
-
- createObject(args.CAMERA, object, args.GROUP)
- }
- setCamera(args) {
- let object = getObject(args.CAMERA)
- object[args.PROPERTY] = args.VALUE
- object.updateProjectionMatrix()
- }
- getCamera(args) {
- let object = getObject(args.CAMERA)
- const value = JSON.stringify(object[args.PROPERTY])
- return value
- }
- renderSceneCamera(args) {
- let object = getObject(args.CAMERA)
- if (!object) return
- camera = object
- //reset composer, else it does not update.
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
-
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true } )
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera( 0.1, 500, cubeRenderTarget )
- createObject(args.CAMERA, cubeCamera, args.GROUP)
-
- renderTargets[args.RT] = {target: cubeRenderTarget, camera: cubeCamera}
- assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture
- }
-
- renderTarget(args) {
- let object = getObject(args.CAMERA)
- const renderTarget = new THREE.WebGLRenderTarget(
- 360,
- 360,
- {
- generateMipmaps: false
- }
- )
-
- renderTargets[args.RT] = {target: renderTarget, camera: object}
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture
- }
- sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H)
- }
- getTarget(args) {
- const t = renderTargets[args.RT].target.texture
- console.log(t, renderTargets[args.RT])
- return `renderTargets/${t.uuid}`
- }
- removeTarget(args) {
- delete(assets.renderTargets[renderTargets[args.RT].target.texture.uuid])
- renderTargets[args.RT].target.dispose()
- delete(renderTargets[args.RT])
- }
- }
- Scratch.extensions.register(new ThreeCameras())
-
- class ThreeObjects {
- getInfo() {
- return {
- id: "threeObjects",
- name: "Three Objects",
- color1: "#38c567ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addObject", blockType: Scratch.BlockType.COMMAND, text: "add object [OBJECT3D] [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},TYPE: {type: Scratch.ArgumentType.STRING, menu: "objectTypes"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "cloneObject", blockType: Scratch.BlockType.COMMAND, text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myClone"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "setObject", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of object [OBJECT3D] to [NAME]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "getObject", blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of object [OBJECT3D]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}}},
- {opcode: "objectE", blockType: Scratch.BlockType.BOOLEAN, text: "is there an object [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "removeObject", blockType: Scratch.BlockType.COMMAND, text: "remove object [OBJECT3D] from scene", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: " ↳ Transforms"},
- {opcode: "setObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
- //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
- //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of [OBJECT3D]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Materials"},
- {opcode: "newMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "new material [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "materialTypes", defaultValue: "MeshStandardMaterial"}}},
- {opcode: "materialE",extensions: ["colours_looks"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a material [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "removeMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "remove material [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "setMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [PROPERTY] of [NAME] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "materialProperties", defaultValue: "color"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
- {opcode: "setBlending",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] blending to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "blendModes"}}},
- {opcode: "setDepth",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] depth to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "depthModes"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Geometries"},
- {opcode: "newGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new geometry [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "geometryTypes", defaultValue: "BoxGeometry"}}},
- {opcode: "geometryE",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a geometry [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "removeGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "remove geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- "---",
- {opcode: "newGeo",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new empty geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoPoints",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] vertex points to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoUVs",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] UVs to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[UVs]"}}},
- "---",
- {opcode: "splines", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create spline [NAME] from curve [CURVE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}}},
- {opcode: "splineModel", extensions: ["colours_operators"], blockType: Scratch.BlockType.COMMAND, text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, MODEL: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}, SPACING: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
- "---",
- {blockType: Scratch.BlockType.BUTTON, text: "Convert font to JSON", func: "openConv"},
- {blockType: Scratch.BlockType.BUTTON, text: "Load JSON font file", func: "loadFont"},
- {opcode: "text", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myText"}, TEXT: {type: Scratch.ArgumentType.STRING, defaultValue: "C-369"}, FONT: {type: Scratch.ArgumentType.STRING, menu: "fonts"}, S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, D: {type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1}, CS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 6}}},
- ],
- menus: {
- objectVector3: {acceptReporters: false, items: [
- {text: "Positon", value: "position"},{text: "Rotation", value: "rotation"},{text: "Scale", value: "scale"},{text: "Facing Direction (.up)", value: "up"}
- ]},
- objectProperties: {acceptReporters: false, items: [
- {text: "Geometry", value: "geometry"},{text: "Material", value: "material"},{text: "Visible (true/false)", value: "visible"},
- ]},
- objectTypes: { acceptReporters: false, items: [
- { text: "Mesh", value: "Mesh" }, { text: "Sprite", value: "Sprite" }, { text: "Points", value: "Points" }, { text: "Line", value: "Line" }, { text: "Group", value: "Group" }
- ]},
- XYZ: {acceptReporters: false, items: [{text: "X", value: "x"},{text: "Y", value: "y"},{text: "Z", value: "z"}]},
- materialProperties: {acceptReporters: false, items: [
- "|GENERAL| <-- not a property",
- { text: "Color", value: "color" },
- { text: "Map", value: "map" },
- { text: "Opacity", value: "opacity" },
- { text: "Transparent", value: "transparent" },
- { text: "Alpha Map", value: "alphaMap" },
- { text: "Alpha Test", value: "alphaTest" },
- { text: "Depth Test", value: "depthTest" },
- { text: "Depth Write", value: "depthWrite" },
- { text: "Color Write", value: "colorWrite" },
- { text: "Side", value: "side" },
- { text: "Visible", value: "visible" },/*
- { text: "Blending", value: "blending" },
- { text: "Blend Src", value: "blendSrc" },
- { text: "Blend Dst", value: "blendDst" },
- { text: "Blend Equation", value: "blendEquation" },
- { text: "Blend Src Alpha", value: "blendSrcAlpha" },
- { text: "Blend Dst Alpha", value: "blendDstAlpha" },
- { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
- { text: "Blend Aplha", value: "blendAplha" },
- { text: "Blend Color", value: "blendColor" },
- { text: "Alpha Hash", value: "alphaHash" },
- { text: "Premultiplied Alpha", value: "premultipliedAlpha" },
-
- { text: "Tone Mapped", value: "toneMapped" },
- { text: "Fog", value: "fog" },
- { text: "Flat Shading", value: "flatShading" },
-
- "|MESH Standard / Physical| <-- not a property",
- { text: "Metalness", value: "metalness" },
- { text: "Metalness Map", value: "metalnessMap" },
- { text: "Roughness", value: "roughness" },
- { text: "Reflectivity", value: "reflectivity" },
- { text: "Roughness Map", value: "roughnessMap" },
- { text: "Emissive", value: "emissive" },
- { text: "Emissive Intensity", value: "emissiveIntensity" },
- { text: "Emissive Map", value: "emissiveMap" },
- { text: "Env Map", value: "envMap" },
- { text: "Env Map Intensity", value: "envMapIntensity" },
- { text: "Env Map Rotation", value: "envMapRotation" },
- { text: "Ior", value: "ior" },
- { text: "Refraction Ratio", value: "refractionRatio" },
- { text: "Clearcoat", value: "clearcoat" },
- { text: "Clearcoat Map", value: "clearcoatMap" },
- { text: "Clearcoat Roughness", value: "clearcoatRoughness" },
- { text: "Clearcoat Roughness Map", value: "clearcoatRoughnessMap" },
- { text: "Dispersion", value: "dispersion" },
- { text: "Sheen", value: "sheen" },
- { text: "Sheen Color", value: "sheenColor" },
- { text: "Sheen Color Map", value: "sheenColorMap" },
- { text: "Sheen Roughness", value: "sheenRoughness" },
- { text: "Sheen Roughness Map", value: "sheenRoughnessMap" },
- { text: "Specular Color", value: "specularColor" },
- { text: "Specular Color Map", value: "specularColorMap" },
- { text: "Specular Intensity", value: "specularIntensity" },
- { text: "Specular Intensity Map", value: "specularIntensityMap" },
- { text: "Transmission", value: "transmission" },
- { text: "Transmission Map", value: "transmissionMap" },
- { text: "Thickness", value: "thickness" },
- { text: "Thickness Map", value: "thicknessMap" },
- { text: "Anisotropy", value: "anisotropy" },
- { text: "Anisotropy Map", value: "anisotropyMap" },
- { text: "Anisotropy Rotation", value: "anisotropyRotation" },
- { text: "Attenuation Distance", value: "attenuationDistance" },
- { text: "Attenuation Color", value: "attenuationColor" },
- { text: "Thickness", value: "thickness" },
- { text: "Iridescence", value: "iridescence" },
- { text: "Iridescence Ior", value: "iridescenceIOR" },
- { text: "Iridescence Map", value: "iridescenceMap" },
- { text: "Iridescence Thickness Range", value: "iridescenceThicknessRange" },
-
- "|MESH Displacement / Normal / Bump| <-- not a property",
- { text: "Displacement Map", value: "displacementMap" },
- { text: "Displacement Scale", value: "displacementScale" },
- { text: "Displacement Bias", value: "displacementBias" },
- { text: "Bump Map", value: "bumpMap" },
- { text: "Bump Scale", value: "bumpScale" },
- { text: "Normal Map Type", value: "normalMapType" },
-
- "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
- { text: "Shininess", value: "shininess" },
-
- { text: "Wireframe", value: "wireframe" },
- { text: "Wireframe Linewidth", value: "wireframeLinewidth" },
- { text: "Wireframe Linecap", value: "wireframeLinecap" },
- { text: "Wireframe Linejoin", value: "wireframeLinejoin" },
-
- "|POINTS| <-- not a property",
- { text: "Size", value: "size" },
- { text: "Size Attenuation", value: "sizeAttenuation" },
-
- "|LINES| <-- not a property",
- { text: "Scale", value: "scale" },
- { text: "Dash Size", value: "dashSize" },
- { text: "Gap Size", value: "gapSize" },
-
- "|SPRITES| <-- not a property",
- { text: "Rotation", value: "rotation" }
-]},
- blendModes: {acceptReporters: false, items: [
- { text: "No Blending", value: "NoBlending" },{ text: "Normal Blending", value: "NormalBlending" },{ text: "Additive Blending", value: "AdditiveBlending" },{ text: "Subtractive Blending", value: "SubtractiveBlending" },{ text: "Multiply Blending", value: "MultiplyBlending" },{ text: "Custom Blending", value: "CustomBlending" }
- ]},
- depthModes: {acceptReporters: false, items: [
- { text: "Never Depth", value: "NeverDepth" },{ text: "Always Depth", value: "AlwaysDepth" },{ text: "Equal Depth", value: "EqualDepth" },{ text: "Less Depth", value: "LessDepth" },{ text: "Less Equal Depth", value: "LessEqualDepth" },{ text: "Greater Equal Depth", value: "GreaterEqualDepth" },{ text: "Greater Depth", value: "GreaterDepth" },{ text: "Not Equal Depth", value: "NotEqualDepth" }
- ]},
- materialTypes:{acceptReporters: false, items: [
- {text:"Mesh Basic Material",value:"MeshBasicMaterial"},{text:"Mesh Standard Material",value:"MeshStandardMaterial"},{text:"Mesh Physical Material",value:"MeshPhysicalMaterial"},{text:"Mesh Lambert Material",value:"MeshLambertMaterial"},{text:"Mesh Phong Material",value:"MeshPhongMaterial"},{text:"Mesh Depth Material",value:"MeshDepthMaterial"},{text:"Mesh Normal Material",value:"MeshNormalMaterial"},{text:"Mesh Matcap Material",value:"MeshMatcapMaterial"},{text:"Mesh Toon Material",value:"MeshToonMaterial"},{text:"Line Basic Material",value:"LineBasicMaterial"},{text:"Line Dashed Material",value:"LineDashedMaterial"},{text:"Points Material",value:"PointsMaterial"},{text:"Sprite Material",value:"SpriteMaterial"},{text:"Shadow Material",value:"ShadowMaterial"}
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- geometryTypes: {acceptReporters: false, items: [
- {text: "Box Geometry", value: "BoxGeometry"},{text: "Sphere Geometry", value: "SphereGeometry"},{text: "Cylinder Geometry", value: "CylinderGeometry"},{text: "Plane Geometry", value: "PlaneGeometry"},{text: "Circle Geometry", value: "CircleGeometry"},{text: "Torus Geometry", value: "TorusGeometry"},{text: "Torus Knot Geometry", value: "TorusKnotGeometry"},
- ]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model! (GLB Loader category)"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- fonts: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.json'))
- if (models.length < 1) return [["Load a font!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
-
- }
- }}
-
- addObject(args) {
- const object = new THREE[args.TYPE]();
-
- object.castShadow = true
- object.receiveShadow = true
-
- createObject(args.OBJECT3D, object, args.GROUP)
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D)
- const clone = object.clone(true)
- clone.name
- createObject(args.NAME, clone, args.GROUP)
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
-
- function degToRad(deg) {
- return deg * Math.PI / 180;
- }
-
-
- if (object.rigidBody) {
- const x = values[0]
- const y = values[1]
- const z = values[2]
- if (args.PROPERTY === "rotation") {
- const euler = new THREE.Euler(
- degToRad(x),
- degToRad(y),
- degToRad(z),
- 'YXZ'
- )
- const quaternion = new THREE.Quaternion()
- quaternion.setFromEuler(euler)
-
- object.rigidBody.setRotation({
- x: quaternion.x,
- y: quaternion.y,
- z: quaternion.z,
- w: quaternion.w
- });
- } else if (args.PROPERTY === "position") {
- object.rigidBody.setTranslation({ x: x, y: y, z: z }, true)
- }
- return
- }
-
- if (object.isCamera == true && controls) {
-
- }
-
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.set(0,0,0)
- }
- if (object.isDirectionalLight == true) {object.pos = new THREE.Vector3(...values); console.log(true, values, object.pos); return}
- object[args.PROPERTY].set(...values);
-
- if (object.type == "CubeCamera") object.updateCoordinateSystem()
- }
- /*
- changeObjectV3(args) {
- getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
-
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.x += values[0]
- object.rotation.y += values[1]
- object.rotation.z += values[2]
- }
- else {
- object[args.PROPERTY].add(...values);
- }
- }
- changeObjectXV3(args) {
- getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "rotation") value = value * Math.PI / 180
-
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let values = vector3ToString(object[args.PROPERTY])
- if (args.PROPERTY === "rotation") {
- const toDeg = Math.PI/180
- values = [values[0]/toDeg,values[1]/toDeg,values[2]/toDeg,]
- }
-
- return JSON.stringify(values)
- }
- setObject(args){
- let object = getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "material") {const mat = materials[args.NAME]; if (mat) value = mat; else value = undefined}
- else if (args.PROPERTY === "geometry") {const geo = geometries[args.NAME]; if (geo) value = geo; else value = undefined}
- else value = !!value
-
- if (value == undefined) return //invalid geo/mat
- object[args.PROPERTY] = value
- }
- getObject(args){
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let value
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value
- }
- removeObject(args) {
- removeObject(args.OBJECT3D)
- }
- objectE(args) {
- return scene.children.map(o => o.name).includes(args.NAME)
- }
-
-//defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert ("material already exists! will replace...")
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
-
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof(args.VALUE) == "string" && args.VALUE.at(0) == "|") return
- const mat = materials[args.NAME]
-
- let value = args.VALUE
-
- if (args.VALUE == "false") value = false
-
- if (args.PROPERTY == "side") {value = (args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide)}
- else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE))
- else value = getAsset(value)
-
-
- console.log("o:", args.VALUE, typeof(args.VALUE))
- console.log("r:", value, typeof(value))
-
- mat[args.PROPERTY] = await (value) //await incase its a texture
- mat.needsUpdate = true
- }
- setBlending(args) {
- const mat = materials[args.NAME]
- mat.blending = THREE[args.VALUE]
- mat.premultipliedAlpha = true
- mat.needsUpdate = true
- }
- setDepth(args) {
- const mat = materials[args.NAME]
- mat.depthFunc = THREE[args.VALUE]
- mat.needsUpdate = true
- }
- removeMaterial(args){
- const mat = materials[args.NAME]
- mat.dispose()
- delete(materials[args.NAME])
- }
- materialE(args) {
- return materials[args.NAME] ? true : false
- }
-
- newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert ("geometry already exists! will replace...")
- const geo = new THREE[args.TYPE]()
- geo.name = args.NAME
-
- geometries[args.NAME] = geo
- }
- setGeometry(args) {
- const geo = geometries[args.NAME]
- geo[args.PROPERTY] = (args.VALUE)
-
- geo.needsUpdate = true;
- }
- removeGeometry(args){
- const geo = geometries[args.NAME]
- geo.dispose()
- delete(geometries[args.NAME])
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false
- }
-
- newGeo(args) {
- const geometry = new THREE.BufferGeometry()
- geometry.name = args.NAME
- geometries[args.NAME] = geometry
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME]
- const positions = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v3 of each vertex of each triangle
-
- geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
- geometry.computeVertexNormals()
-
- geometry.needsUpdate = true
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME]
- const UVs = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v2 of each UV of each triangle
-
- geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(UVs), 2))
- geometry.needsUpdate = true
- }
-
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE))
- geometry.name = args.NAME
-
- geometries[args.NAME] = geometry
- }
-
- async splineModel(args) {
- const model = await getModel(args.MODEL, args.NAME)
- if (!model) return console.warn("Model not found:", args.MODEL)
-
- const curve = getAsset(args.CURVE)
- const spacing = parseFloat(args.SPACING) || 1
- const curveLength = curve.getLength()
- const divisions = Math.floor(curveLength / spacing)
-
- const geomList = []
- const matList = []
-
- for (let i = 0; i <= divisions; i++) {
- const t = i / divisions
- const pos = curve.getPointAt(t)
- const tangent = curve.getTangentAt(t)
-
- const temp = model.clone(true)
- temp.position.copy(pos)
-
- const up = new THREE.Vector3(0, 0, 1)
- const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize())
- temp.quaternion.copy(quat)
-
- temp.updateMatrixWorld(true)
-
- temp.traverse(child => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone()
- geom.applyMatrix4(child.matrixWorld)
- geomList.push(geom)
- matList.push(child.material) //.clone() ?
- }
- })
- }
-
- const validGeoms = geomList.filter(g => {
- const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position
- if (!ok) console.warn("geometry skipped:", g)
- return ok
- })
-
- const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true)
- merged.computeBoundingBox()
- merged.computeBoundingSphere()
-
- merged.name = args.NAME
- geometries[args.NAME] = merged
- matList.name = args.NAME
- materials[args.NAME] = matList
- }
-
- async text(args) {
- const fontFile = runtime.getTargetForStage().getSounds().find(c => c.name === args.FONT)
- if (!fontFile) return
-
- const json = new TextDecoder().decode(fontFile.asset.data.buffer)
- const fontData = JSON.parse(json)
-
- const font = fontLoad.parse(fontData)
-
- const params = {font: font, size: JSON.parse(args.S), height: JSON.parse(args.D), curveSegments: JSON.parse(args.CS), bevelEnabled: false}
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params)
- geometry.computeVertexNormals()
- geometry.center() // optional, recenters the text
-
-
- geometry.name = args.NAME
-
- geometries[args.NAME] = geometry
- }
-
- async loadFont() {
- openFileExplorer(".json").then(files => {
- const file = files[0]
- const reader = new FileReader()
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result
-
- // From lily's assets
- // // Thank you PenguinMod for providing this code.
-
- const targetId = runtime.getTargetForStage().id //util.target.id not working!
- const assetName = Cast.toString(file.name)
-
- const buffer = arrayBuffer
-
- const storage = runtime.storage
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- )
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- )
- alert("Font loaded successfully!")
- } catch (e) {
- console.error(e)
- alert("Error loading font.")
- }
-
- // End of PenguinMod
- }
-
- reader.readAsArrayBuffer(file);
- })
- }
- openConv() {{open("https://gero3.github.io/facetype.js/")}}
-
- }
- Scratch.extensions.register(new ThreeObjects())
-
- class ThreeLights {
- getInfo() {
- return {
- id: "threeLights",
- name: "Three Lights",
- color1: "#c7a22aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addLight", blockType: Scratch.BlockType.COMMAND, text: "add light [NAME] type [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "lightTypes"}}},
- {opcode: "setLight", blockType: Scratch.BlockType.COMMAND, text: "set light [NAME][PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lightProperties", defaultValue: "intensity"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1", exemptFromNormalization: true}}},
- ],
- menus: {
- lightTypes: {acceptReporters: false, items: [
- {text: "Ambient Light", value: "AmbientLight"},{text: "Directional Light", value: "DirectionalLight"},{text: "Point Light", value: "PointLight"},{text: "Hemisphere Light", value: "HemisphereLight"},{text: "Spot Light", value: "SpotLight"},
- ]},
- lightProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Intensity", value: "intensity"},{text: "Cast Shadow?", value: "castShadow"},
- {text: "Ground Color (HemisphereLight)", value: "groundColor"},
- {text: "Map (SpotLight)", value: "map"},{text: "Distance (SpotLight)", value: "distance"},{text: "Decay (SpotLight)", value: "decay"},{text: "Penumbra (SpotLight)", value: "penumbra"},{text: "Angle/Size (SpotLight)", value: "angle"},{text: "Power (SpotLight)", value: "power"},
- {text: "Target Position (Directional/SpotLight)", value: "target"},
- ]},
- }
- }}
-
- addLight(args) {
- const light = new THREE[args.TYPE](0xffffff, 1)
-
- createObject(args.NAME, light, args.GROUP)
- lights[args.NAME] = light
- if (light.type === "AmbientLight" || "HemisphereLight") return
-
- light.castShadow = true
- if (light.type === "PointLight") return
- //Directional & Spot Light
- light.target.position.set(0, 0, 0)
- scene.add(light.target)
-
- light.pos = new THREE.Vector3(0,0,0)
-
- light.shadow.mapSize.width = 4096
- light.shadow.mapSize.height = 2048
-
- if (light.type === "SpotLight") {
- light.decay = 0
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true
- light.needsUpdate = true
- }
-
- setLight(args) {
- const light = lights[args.NAME]
- if (!args.PROPERTY) return
- if (args.PROPERTY === "target") {
- light.target.position.set(...JSON.parse(args.VALUE)) //vector3
- light.target.updateMatrixWorld();
- }
- else {
- light[args.PROPERTY] = getAsset(args.VALUE)
- }
- light.needsUpdate = true
-
- if (light.type === "AmbientLight" || "HemisphereLight") return
-
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true
- }
-
- }
- Scratch.extensions.register(new ThreeLights())
-
- class ThreeUtilities {
- getInfo() {
- return {
- id: "threeUtility",
- name: "Three Utilities",
- color1: "#3875c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newVector2", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}}},
- {opcode: "newVector3", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y] [Z]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}, Z: {type: Scratch.ArgumentType.NUMBER}}},
- "---",
- {opcode: "operateV3", blockType: Scratch.BlockType.REPORTER, text: "do [V3] [O] [V32]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, O: {type: Scratch.ArgumentType.STRING, menu: "operators"}, V32: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "moveVector3", blockType: Scratch.BlockType.REPORTER, text: "move [S] steps in vector [V3] in direction [D3]", arguments: {S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "directionTo", blockType: Scratch.BlockType.REPORTER, text: "direction from [V3] to [T3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, T3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
- "---",
- {opcode: "newColor", blockType: Scratch.BlockType.REPORTER, text: "New Color [HEX]", arguments: {HEX: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff"}}},
- {opcode: "newFog", blockType: Scratch.BlockType.REPORTER, text: "New Fog [COLOR] [NEAR] [FAR]", arguments: {COLOR: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff", exemptFromNormalization: true}, NEAR: {type: Scratch.ArgumentType.NUMBER}, FAR: {type: Scratch.ArgumentType.NUMBER, defaultValue: 10}}},
- {opcode: "newTexture", blockType: Scratch.BlockType.REPORTER, text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newCubeTexture", blockType: Scratch.BlockType.REPORTER, text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]", arguments: {"COSTUMEX0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEX1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ1": {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newEquirectangularTexture", blockType: Scratch.BlockType.REPORTER, text: "New Equirectangular Texture [COSTUME] [MODE]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"}}},
- "---",
- {opcode: "curve", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]", arguments: {TYPE: {type: Scratch.ArgumentType.STRING, menu: "curveTypes"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]"}, CLOSED: {type: Scratch.ArgumentType.STRING, defaultValue: "true"}}},
- "---",
- {opcode:"mouseDown",extensions: ["colours_sensing"], blockType: Scratch.BlockType.BOOLEAN, text: "mouse [BUTTON] [action]?", arguments: {BUTTON: {type: Scratch.ArgumentType.STRING, menu: "mouseButtons"},action: {type: Scratch.ArgumentType.STRING, menu: "mouseAction"}}},
- {opcode: "mousePos",extensions: ["colours_sensing"], blockType: Scratch.BlockType.REPORTER, text: "mouse position", arguments: {}},
- "---",
- {opcode: "getItem",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "get item [ITEM] of [ARRAY]", arguments: {ITEM: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}, ARRAY: {type: Scratch.ArgumentType.STRING, defaultValue: `["myObject", "myLight"]`}}},
- {blockType: Scratch.BlockType.LABEL, text: "↳ Raycasting"},
- {opcode: "raycast", blockType: Scratch.BlockType.COMMAND, text: "Raycast from [V3] in direction [D3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,1]"}}},
- {opcode: "getRaycast", blockType: Scratch.BlockType.REPORTER, text: "get raycast [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "raycastProperties"}}},
-
- ],
- menus: {
- materialProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Map (texture)", value: "map"},{text: "Alpha Map (texture)", value: "alphaMap"},{text: "Alpha Test (0-1)", value: "alphaTest"},{text: "Side (front/back/double)", value: "side"},{text: "Bump Map (texture)", value: "bumpMap"},{text: "Bump Scale", value: "bumpScale"},
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- raycastProperties: {acceptReporters: false, items: [
- {text: "Intersected Object Names", value: "name"},{text: "Number of Objects", value: "number"},{text: "Intersected Objects distances", value: "distance"},
- ]},
- mouseButtons: {acceptReporters: false, items: ["left","middle","right"]},
- mouseAction: {acceptReporters: false, items: ["Down","Clicked"]},
- curveTypes: {acceptReporters: false, items: ["CatmullRomCurve3"]},
- operators: {acceptReporters: false, items: [
- "+","-","*","/","=","max","min","dot","cross","distance to","angle to","apply euler",
- ]}
- }
- }}
- mouseDown(args) {
- if (args.action === "Down") return isMouseDown[args.BUTTON]
- if (args.action === "Clicked") {
- if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false
- else prevMouse[args.BUTTON] = true; return true
- }
- }
- mousePos(event) {
- return JSON.stringify(mouseNDC)
- }
- newVector3(args) {
- return JSON.stringify([args.X, args.Y, args.Z])
- }
- operateV3(args){
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const v32 = new THREE.Vector3(...JSON.parse(args.V32))
-
- let r
- if (args.O == "+") r = v3.add(v32)
- else if (args.O == "-") r = v3.sub(v32)
- else if (args.O == "*") r = v3.multiply(v32)
- else if (args.O == "/") r = v3.divide(v32)
- else if (args.O == "=") r = v3.equals(v32)
- else if (args.O == "max") r = v3.max(v32)
- else if (args.O == "min") r = v3.min(v32)
- else if (args.O == "dot") r = v3.dot(v32)
- else if (args.O == "cross") r = v3.cross(v32)
- else if (args.O == "distance to") r = v3.distanceTo(v32)
- else if (args.O == "angle to") r = v3.angleTo(v32)
- else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder))
-
- if (typeof(r) == "object") return JSON.stringify([r.x, r.y, r.z])
- else return JSON.stringify(r)
- }
-
- newVector2(args) {
- return JSON.stringify([args.X, args.Y])
- }
-
- moveVector3(args) {
- const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
- const steps = Number(args.S);
-
- const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
-
- const yaw = THREE.MathUtils.degToRad(yawInputDeg);
- const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
- const roll = THREE.MathUtils.degToRad(rollInputDeg);
-
- const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
-
- const forwardVector = new THREE.Vector3(0, 0, -1);
- const direction = forwardVector.applyEuler(euler).normalize();
-
- const newPos = currentPos.add(direction.multiplyScalar(steps));
- return JSON.stringify([newPos.x, newPos.y, newPos.z]);
- }
-
- directionTo(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const toV3 = new THREE.Vector3(...JSON.parse(args.T3))
-
- const direction = toV3.clone().sub(v3).normalize();
- // Pitch (X)
- const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x*direction.x + direction.z*direction.z));
- // Yaw (Y)
- const yaw = Math.atan2(direction.x, direction.z);
-
- // Roll always 0
- return JSON.stringify([180+THREE.MathUtils.radToDeg(pitch),THREE.MathUtils.radToDeg(yaw),0])
- }
-
- newColor(args) {
- const color = new THREE.Color(args.HEX)
- const uuid = crypto.randomUUID()
- assets.colors[uuid] = color
- return `colors/${uuid}`
- }
- newFog(args) {
- const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR)
- const uuid = crypto.randomUUID()
- assets.fogs[uuid] = fog
- return `fogs/${uuid}`
- }
- async newTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newCubeTexture(args) {
- const uris = [encodeCostume(args.COSTUMEX0),encodeCostume(args.COSTUMEX1), encodeCostume(args.COSTUMEY0),encodeCostume(args.COSTUMEY1), encodeCostume(args.COSTUMEZ0),encodeCostume(args.COSTUMEZ1)]
- const normalized = await Promise.all(uris.map(uri => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
- texture.mapping = THREE.EquirectangularReflectionMapping
-
- setTexutre(texture, args.MODE)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
-
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g)
- if (!matches) return []
-
- return matches.map(str => {
- const nums = str
- .replace(/[\[\]\s]/g, '')
- .split(',')
- .map(Number)
- return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0)
- })
- }
- const points = parsePoints(args.POINTS)
- const curve = new THREE[args.TYPE](points)
- curve.closed = JSON.parse(args.CLOSED)
-
- const uuid = crypto.randomUUID()
- assets.curves[uuid] = curve
- return `curves/${uuid}`
- }
-
- getItem(args) {
- const items = JSON.parse(args.ARRAY)
- const item = items[args.ITEM - 1]
- if (!item) return "0"
- return item
- }
-
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3))
- // rotation is in degrees => convert to radians first
- const rot = JSON.parse(args.D3).map(v => v * Math.PI / 180)
-
- const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder)
- const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize()
-
- const raycaster = new THREE.Raycaster()
- //const camera = getObject(args.CAMERA)
- raycaster.set( origin, direction );
-
- const intersects = raycaster.intersectObjects( scene.children, true )
-
- raycastResult = intersects
- }
- getRaycast(args) {
- if (args.PROPERTY === "number") return raycastResult.length
- if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map(i => i.distance))
- return JSON.stringify(raycastResult.map(i => i.object[args.PROPERTY]))
- }
-
- }
- Scratch.extensions.register(new ThreeUtilities())
-
- class ThreeGLB {
- getInfo() {
- return {
- id: "threeGLB",
- name: "Three GLB Loader",
- color1: "#c53838ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Load GLB File", func: "loadModelFile"},
- {opcode: "addModel", blockType: Scratch.BlockType.COMMAND, text: "add [ITEM] as [NAME] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},ITEM: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}}},
- {opcode: "getModel", blockType: Scratch.BlockType.REPORTER, text: "get object [PROPERTY] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "modelProperties"}}},
- {opcode: "playAnimation", blockType: Scratch.BlockType.COMMAND, text: "play animation [ANAME] of [NAME], [TIMES] times", arguments: {TIMES: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "pauseAnimation", blockType: Scratch.BlockType.COMMAND, text: "set [TOGGLE] animation [ANAME] of [NAME]", arguments: {TOGGLE: {type: Scratch.ArgumentType.NUMBER, menu: "pauseUn"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "stopAnimation", blockType: Scratch.BlockType.COMMAND, text: "stop animation [ANAME] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
-
- ],
- menus: {
- modelProperties: {acceptReporters: false, items: [
- {text: "Animations", value: "animations"},
- ]},
- pauseUn: {acceptReporters: true, items: [{text: "Pause", value: "true"},{text: "Unpasue", value: "false"},]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- }
- }}
-
- async loadModelFile() {
-
- openFileExplorer(".glb").then(files => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- { // From lily's assets
-
- // Thank you PenguinMod for providing this code.
- {
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- //const res = await Scratch.fetch(args.URL);
- //const buffer = await res.arrayBuffer();
- const buffer = arrayBuffer
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Model loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading model.");
- }
- }
- // End of PenguinMod
- }
- };
-
- reader.readAsArrayBuffer(file);
- })
-
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME)
-
- createObject(args.NAME, group, args.GROUP)
- }
- getModel(args){
- if (!models[args.NAME]) return;
- return Object.keys(models[args.NAME].actions).toString()
- }
-
- playAnimation(args) {
- const model = models[args.NAME]
- if (!model) {console.log("no model!"); return}
-
- const action = model.actions[args.ANAME] //clones of models dont have a stored actions!
- if (!action) {
- console.log("no action!")
- return
- }
-
- args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity)
-
- action.reset()
- .play()
- }
- stopAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.stop();
- }
- pauseAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.paused = args.TOGGLE
- }
-
- }
- Scratch.extensions.register(new ThreeGLB())
-
- class ThreeAddons {
- getInfo() {
- return {
- id: "threeAddons",
- name: "Three Addons",
- color1: "#c538a2ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.LABEL, text: "Orbit Control"},
- {opcode: "OrbitControl", blockType: Scratch.BlockType.COMMAND, text: "set addon Orbit Control [STATE]", arguments: {STATE: {type: Scratch.ArgumentType.STRING, menu: "onoff"},}},
-
- {blockType: Scratch.BlockType.LABEL, text: "Post Processing"},
- {opcode: "resetComposer", blockType: Scratch.BlockType.COMMAND, text: "reset composer"},
- {opcode: "bloom", blockType: Scratch.BlockType.COMMAND, text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},I: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, T:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "godRays", blockType: Scratch.BlockType.COMMAND, text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}, DEC:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.95}, DENS:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},EXP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1},WEI:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.4},RES:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},SAMP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 64},}},
- {opcode: "dots", blockType: Scratch.BlockType.COMMAND, text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, A: {type: Scratch.ArgumentType.ANGLE, defaultValue: 0},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "depth", blockType: Scratch.BlockType.COMMAND, text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]", arguments: {FD: {type: Scratch.ArgumentType.NUMBER, defaultValue: (3)},FL: {type: Scratch.ArgumentType.NUMBER, defaultValue: (0.001)},BS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 4},H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 240},OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}}},
- "---",
- {opcode: "custom", blockType: Scratch.BlockType.COMMAND, text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myShader"}, FRA: {type: Scratch.ArgumentType.STRING}, VER: {type: Scratch.ArgumentType.STRING}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}, OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
- ],
- menus: {
- onoff: {acceptReporters: true, items: [{text: "enabled", value: "1"},{text: "disabled", value: "0"},]},
- blendModes: {acceptReporters: false, items: [
- "SKIP","SET","ADD","ALPHA","AVERAGE","COLOR","COLOR_BURN","COLOR_DODGE",
- "DARKEN","DIFFERENCE","DIVIDE","DST","EXCLUSION","HARD_LIGHT","HARD_MIX",
- "HUE","INVERT","INVERT_RGB","LIGHTEN","LINEAR_BURN","LINEAR_DODGE",
- "LINEAR_LIGHT","LUMINOSITY","MULTIPLY","NEGATION","NORMAL","OVERLAY",
- "PIN_LIGHT","REFLECT","SCREEN","SRC","SATURATION","SOFT_LIGHT","SUBTRACT",
- "VIVID_LIGHT"
- ]},
- }
- }}
-
- OrbitControl(args) {
- if (controls) controls.dispose()
-
- console.log("creating...", OrbitControls)
- controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
- controls.enableDamping = true
-
- controls.enabled = !!args.STATE
- console.log(controls)
- }
-
- resetComposer() {
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
-
- bloom(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const bloomEffect = new BloomEffect({
- intensity: args.I,
- luminanceThreshold: args.T, // ← correct key
- luminanceSmoothing: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- bloomEffect.blendMode.opacity.value = args.OP
-
- const pass = new EffectPass(camera, bloomEffect)
-
- composer.addPass(pass)
- }
-
- godRays(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- let object = getObject(args.NAME)
- const sun = object
-
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- })
- godRays.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, godRays)
- composer.addPass(pass)
- }
-
- dots(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dot = new DotScreenEffect({
- angle: args.A,
- scale: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- dot.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, dot)
- composer.addPass(pass)
- }
-
- depth(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- })
- dofEffect.blendMode.opacity.value = args.OP
-
- const dofPass = new EffectPass(camera, dofEffect)
- composer.addPass(dofPass)
- }
-
- async custom(args) {
- function cleanGLSL(glslCode) {
- //delete multilines comments
- let cleanedCode = glslCode.replace(/\/\*[\s\S]*?\*\//g, ' ')
- .replace(/ /g, '\n')
- .replace(/\/\/.*$/gm, ' ')
- .replace(/; /g, ';\n')
-
- return cleanedCode;
- }
-
- let fs = cleanGLSL(`
- ${args.FRA}
- `)
- if (!args.FRA.trim()) {fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`}
- const vs = cleanGLSL(`
- ${args.VER}
- `)
- console.log(fs)
- console.log(vs)
-
- const effect = new Effect(
- "Custom",
- fs,
- {
- blendFunction: BlendFunction[args.BLEND],
- vertexShader: vs,
- uniforms: new Map([ //uniforms usually in shaders... open to more!
- ['time', new THREE.Uniform(0.0)],
- ['resolution', new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height))]
- ]),
- defines: new Map([['USE_TIME', '1'], ['USE_VERTEX_TRANSFORM', '']]),
- }
- );
-
- effect.blendMode.opacity.value = args.OP
-
- const pass = new EffectPass(camera, effect);
- composer.addPass(pass);
-
- customEffects.push(effect);
- }
-
- }
- Scratch.extensions.register(new ThreeAddons())
-
- class RapierPhysics {
- getInfo() {
- return {
- id: "rapierPhysics",
- name: "RAPIER Physics",
- color1: "#222222",
- color2: "#203024ff",
- color3: "#78f07eff",
- blocks: [
- {opcode: "createWorld", blockType: Scratch.BlockType.COMMAND, text: "create world | gravity:[G]", arguments: {G: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,-9.81,0]"}}},
- {opcode: "getWorld", blockType: Scratch.BlockType.REPORTER, text: "get world [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "wProp"}}},
- "---",
- {opcode: "objectPhysics", blockType: Scratch.BlockType.COMMAND, text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]", arguments: {state2: {type: Scratch.ArgumentType.STRING, menu: "state2"},state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"}, type: {type: Scratch.ArgumentType.STRING, menu: "objectTypes", defaultValue: "dynamic"}, collider: {type: Scratch.ArgumentType.STRING, menu: "colliderTypes", defaultValue: "cuboid"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},mass: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},density: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},friction: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0.5"}}},
- "---",
- {blockType: Scratch.BlockType.LABEL, text: "- RigidBody"},
- {opcode: "setRB", blockType: Scratch.BlockType.COMMAND, text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodySets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getRB", blockType: Scratch.BlockType.REPORTER, text: "get rigidbody [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodyProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "lockObjectAxis", blockType: Scratch.BlockType.COMMAND, text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]", arguments: {OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lockAxes"}, X: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Y: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Z: {type: Scratch.ArgumentType.STRING, menu: "tf"}}},
- "---",
- {opcode: "addForce", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,10,0]"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "forces", defaultValue: "addForce"},SPACE: {type: Scratch.ArgumentType.STRING, menu: "spaces", defaultValue: "world"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "resetForces", blockType: Scratch.BlockType.COMMAND, text: "reset [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "resetF", defaultValue: "resetForces"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "enableCCD", blockType: Scratch.BlockType.COMMAND, text: "enable Continuous Collision Detection for [OBJECT] [state]", arguments: {state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "oPropS", defaultValue: "physics"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "fixedJoint", blockType: Scratch.BlockType.COMMAND, text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},RA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},RB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},}},
- {opcode: "sphericalJoint", blockType: Scratch.BlockType.COMMAND, text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},}},
- {opcode: "revoluteJoint", blockType: Scratch.BlockType.COMMAND, text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},X: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"},}},
- "---",
- {blockType: Scratch.BlockType.LABEL, text: "- Collider"},
- {opcode: "setC", blockType: Scratch.BlockType.COMMAND, text: "set collider [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderSets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getC", blockType: Scratch.BlockType.REPORTER, text: "get collider [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "sensorSingle", blockType: Scratch.BlockType.BOOLEAN, text: "is sensor [SENSOR] touching [OBJECT]?", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "sensorAll", blockType: Scratch.BlockType.REPORTER, text: "objects touching sensor [SENSOR]", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}}}
- ],
- menus: {
- wProp: {acceptReporters: false, items: [
- {text: "Gravity", value: "gravity"}, {text: "log to console", value: "log"}
- ]},
- tf: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true", value: "true"}]},
- lockAxes: {acceptReporters: false, items: [
- {text: "Translation", value: "setEnabledTranslations"}, {text: "Rotation", value: "setEnabledRotations"}
- ]},
- rigidBodyProperties: {acceptReporters: false, items: [
- {text: "Type", value: "bodyType"},
- {text: "Linear Velocity", value: "linvel"},
- {text: "Angular Velocity", value: "angvel"},
- {text: "Translation (position)", value: "translation"},
- {text: "Rotation (quaternion)", value: "rotation"},
- {text: "Mass", value: "mass"},
- //{text: "Center of Mass", value: "centerOfMass"},
- {text: "Linear Damping", value: "linearDamping"},
- {text: "Angular Damping", value: "angularDamping"},
- {text: "Is Sleeping?", value: "isSleeping"},
- //{text: "Can Sleep?", value: "isCanSleep"},
- {text: "Gravity Scale", value: "gravityScale"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"},
- //{text: "Sleeping", value: "sleeping"}
- ]},
- rigidBodySets: {acceptReporters: false, items: [
- //{text: "Linear Velocity", value: "setLinvel"},
- //{text: "Angular Velocity", value: "setAngvel"},
- //{text: "Mass", value: "setMass"},
- {text: "Gravity Scale", value: "setGravityScale"},
- //{text: "Can Sleep?", value: "setCanSleep"},
- //{text: "Sleeping", value: "sleeping"},
- {text: "Linear Damping", value: "setLinearDamping"},
- {text: "Angular Damping", value: "setAngularDamping"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"}
- ]},
- colliderProperties: {acceptReporters: false, items: [
- //{text: "Collider Type", value: "type"},
- {text: "Is Sensor?", value: "isSensor"},
- {text: "Friction", value: "friction"},
- {text: "Restitution", value: "restitution"},
- {text: "Density", value: "density"},
- {text: "Mass", value: "mass"},
- {text: "Position", value: "translation"},
- {text: "Rotation", value: "rotation"},
- //{text: "Area", value: "area"},
- {text: "Volume", value: "volume"},
- {text: "Collision Groups", value: "collisionGroups"},
- //{text: "Collision Mask", value: "collisionMask"},
- //{text: "Is Enabled?", value: "enabled"},
- //{text: "Contact Count", value: "contactCount"},
- //{text: "RigidBody Handle", value: "rigidBody"}
- ]},
- colliderSets: {acceptReporters: false, items: [
- {text: "Friction", value: "setFriction"},
- {text: "Restitution", value: "setRestitution"},
- {text: "Density", value: "setDensity"},
- {text: "Is Sensor?", value: "setSensor"},
- {text: "Collision Groups", value: "setCollisionGroups"},
- //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
- //{text: "Position Offset", value: "setTranslation"},
- //{text: "Rotation Offset", value: "setRotation"}
- ]},
- state: {acceptReporters: true, items: [{text: "on", value: "true"},{text: "off", value: "false"}]},
- state2: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true (must be fixed)", value: "true"}]},
- spaces: {acceptReporters: false, items: [{text: "World", value: "world"},{text: "Local", value: "local"}]},
- objectTypes: {acceptReporters: false, items: [{text: "Dynamic", value: "dynamic"},{text: "Fixed", value: "fixed"},{text: "Kinematic Position Based",value: "kinematicPositionBased"}]},
- colliderTypes: {acceptReporters: false, items: [{text: "Box, Rectangle, cuboid", value: "cuboid"},{text: "Sphere, ball", value: "ball"},{text: "Custom, complex simple shapes, convexHull", value: "convexHull"},{text:"Precision, TriMesh",value:"trimesh"}]},
- forces: {acceptReporters: false, items: [{text: "Force", value: "addForce"},{text: "Torque (rotation)", value: "addTorque"},{text: "Apply Impulse", value: "applyImpulse"},{text: "Apply Torque Impulse (rotation)", value: "applyTorqueImpulse"},{text: "Linear Velocity", value: "setLinvel"},{text: "Angular Velocity", value: "setAngvel"},]},
- resetF: {acceptReporters: false, items: [{text:"Forces", value: "resetForces"},{text:"Torques", value: "resetTorques"},]}
- }
- }
- }
- joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true)
- }
-
- fixedJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- let RA = JSON.parse(args.RA).map(Number)
- let RB = JSON.parse(args.RB).map(Number)
-
- RA = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RA[0]),
- THREE.MathUtils.degToRad(RA[1]),
- THREE.MathUtils.degToRad(RA[2])
- )
- )
- RB = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RB[0]),
- THREE.MathUtils.degToRad(RB[1]),
- THREE.MathUtils.degToRad(RB[2])
- )
- )
-
- const data = RAPIER.JointData.fixed(
- { x: VA[0], y: VA[1], z: VA[2] }, RA,
- { x: VB[0], y: VB[1], z: VB[2] }, RB
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- sphericalJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
-
- const data = RAPIER.JointData.spherical(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- revoluteJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.revolute(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- prismaticJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.prismatic(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- createWorld(args) {
- const v3 = JSON.parse(args.G).map(Number)
- const gravity = { x: v3[0], y: v3[1], z: v3[2]}
- physicsWorld = new RAPIER.World(gravity)
-
- console.log(physicsWorld)
- }
-
- getWorld(args) {
- if (args.PROPERTY === "log") {console.log(physicsWorld); return "logged"}
- return JSON.stringify(physicsWorld[args.PROPERTY])
- }
-
- setRB(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.rigidBody[args.PROPERTY](value)
- }
- setC(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.collider[args.PROPERTY](value)
- }
-
- getRB(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.rigidBody[args.PROPERTY]())
- }
- getC(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.collider[args.PROPERTY]())
- }
-
- lockObjectAxis(args) {
- let object = getObject(args.OBJECT)
- const x = !JSON.parse(args.X)
- const y = !JSON.parse(args.Y)
- const z = !JSON.parse(args.Z)
- object.rigidBody[args.PROPERTY](x,y,z,true) //changes is xyz, wake up
- }
-
- objectPhysics(args) {
- let object = getObject(args.OBJECT)
- object.physics = JSON.parse(args.state)
-
- if (JSON.parse(args.state)) {
- //if already exists delete:
- if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
- }
- /*asing a rigidbody and collider to object and add them to physicsWorld*/
- let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
- .setTranslation(object.position.x, object.position.y, object.position.z)
- .setRotation({w: object.quaternion._w, x: object.quaternion._x, y: object.quaternion._y, z: object.quaternion._z})
-
- let colliderDesc
- switch(args.collider) {
- case "cuboid": colliderDesc = createCuboidCollider(object,); break
- case "ball": colliderDesc = createBallCollider(object); break
- case "convexHull": colliderDesc = createConvexHullCollider(object); break
- case "trimesh": colliderDesc = TriMesh(object); break
- }
- colliderDesc.setSensor(JSON.parse(args.state2)).setMass(args.mass).setDensity(args.density).setFriction(args.friction)
-
- let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc)
- let collider = physicsWorld.createCollider(colliderDesc, rigidBody)
-
- object.rigidBody = rigidBody
- object.collider = collider
- } else {
- /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
- }
-
- }
-
- enableCCD(args) {
- let object = getObject(args.OBJECT)
- if (object.physics) {
- let rigidBody = object.rigidBody
- rigidBody.enableCcd(JSON.parse(args.state))
- }
- }
-
- addForce(args) {
- let object = getObject(args.OBJECT)
- const vector = JSON.parse(args.VALUE).map(Number)
-
- let force = new THREE.Vector3(vector[0],vector[1],vector[2])
- if (args.SPACE === "local") {
- force.applyQuaternion(object.quaternion);
- }
-
- object.rigidBody[args.PROPERTY](force,true)
- }
-
- resetForces(args) {
- rigidBody[args.PROPERTY](true)
- }
-
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR)
-
- let object = getObject(args.OBJECT)
-
- let touching = false
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- if (otherCollider === object.collider) touching = true
- })
-
- return touching
- }
-
- sensorAll(args) {
- const sensor = getObject(args.SENSOR)
-
- const touchedObjects = []
-
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- // find owner of collider
- const otherObject = scene.children.find(o => o.collider === otherCollider)
- console.log(otherCollider)
- if (otherObject) touchedObjects.push(otherObject.name)
- })
-
- return JSON.stringify(touchedObjects)
- }
-
- }
- Scratch.extensions.register(new RapierPhysics())
-
- //Thanks to the PointerLock extension of Turbowarp
- const mouse = vm.runtime.ioDevices.mouse;
- let isLocked = false;
- let isPointerLockEnabled = false;
-
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
-
- const postMouseData = (e, isDown) => {
- const { movementX, movementY } = e;
- const { width, height } = rect;
- const x = mouse._clientX + movementX;
- const y = mouse._clientY - movementY;
- mouse._clientX = x;
- mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
- mouse._clientY = y;
- mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
- if (typeof isDown === "boolean") {
- const data = {
- button: e.button,
- isDown,
- };
- originalPostIOData(data);
- }
- };
-
- const mouseDevice = vm.runtime.ioDevices.mouse;
- const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
- mouseDevice.postData = (data) => {
- if (!isPointerLockEnabled) {
- return originalPostIOData(data);
- }
- };
-
- document.addEventListener(
- "mousedown",
- (e) => {
- // @ts-expect-error
- if (threeRenderer.domElement.contains(e.target)) {
- if (isLocked) {
- postMouseData(e, true);
- } else if (isPointerLockEnabled) {
- threeRenderer.domElement.requestPointerLock();
- }
- }
- },
- true
- );
- document.addEventListener(
- "mouseup",
- (e) => {
- if (isLocked) {
- postMouseData(e, false);
- // @ts-expect-error
- } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
- threeRenderer.domElement.requestPointerLock();
- }
- },
- true
- );
- document.addEventListener(
- "mousemove",
- (e) => {
- if (isLocked) {
- postMouseData(e);
- }
- },
- true
- );
-
- document.addEventListener("pointerlockchange", () => {
- isLocked = document.pointerLockElement === threeRenderer.domElement;
- });
- document.addEventListener("pointerlockerror", (e) => {
- console.error("Pointer lock error", e);
- });
-
- const oldStep = vm.runtime._step;
- vm.runtime._step = function (...args) {
- const ret = oldStep.call(this, ...args);
- if (isPointerLockEnabled) {
- const { width, height } = rect;
- mouse._clientX = width / 2;
- mouse._clientY = height / 2;
- mouse._scratchX = 0;
- mouse._scratchY = 0;
- }
- return ret;
- };
-
- vm.runtime.on("PROJECT_LOADED", () => {
- isPointerLockEnabled = false;
- if (isLocked) {
- document.exitPointerLock();
- }
- });
-
- class Pointerlock {
- getInfo() {
- return {
- id: "threepointerlockmod",
- name: "Pointerlock for Extra 3D",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setLocked", blockType: Scratch.BlockType.COMMAND, text: "set pointer lock [enabled]", arguments: { enabled: { type: Scratch.ArgumentType.STRING, defaultValue: "true", menu: "enabled"}},},
- {opcode: "isLocked", blockType: Scratch.BlockType.BOOLEAN, text: "pointer locked?",},
- ],
- menus: {
- enabled: {acceptReporters: true, items: [
- {text: "enabled", value: "true"},{text: "disabled", value: "false"},
- ]}
- },
- }
- }
-
- setLocked(args) {
- isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
- if (!isPointerLockEnabled && isLocked) {
- document.exitPointerLock();
- }
- }
-
- isLocked() {
- return isLocked;
- }
- }
-Scratch.extensions.register(new Pointerlock())
-
- })
-
-
-
-
-})(Scratch);
diff --git a/threejsD_LOCAL_13852.js b/threejsD_LOCAL_13852.js
deleted file mode 100644
index 27ba380..0000000
--- a/threejsD_LOCAL_13852.js
+++ /dev/null
@@ -1,2414 +0,0 @@
-// Name: Extra 3D
-// ID: threejsExtension
-// Description: Use three js inside Turbowarp! A 3D graphics library.
-// By: Civero
-// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
-
-(function (Scratch) {
- "use strict";
-
- if (!Scratch.extensions.unsandboxed) {
- throw new Error("Three-D extension must run unsandboxed");
- }
-
- if (Scratch.vm.runtime.isPackaged) {alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`); return}
- //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
-
- const vm = Scratch.vm;
- const runtime = vm.runtime
- const renderer = Scratch.renderer;
- const canvas = renderer.canvas
- const Cast = Scratch.Cast;
- const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
-
- let alerts = false
- console.log("alerts are "+ (alerts ? "enabled" : "disabled"))
-
- let isMouseDown = { left: false, middle: false, right: false }
- let prevMouse = { left: false, middle: false, right: false }
-
- let lastWidth = 0
- let lastHeight = 0
-
- let THREE
- let clock
- let running
- let loopId
- //Addons
- let GLTFLoader
- let gltf
- let OrbitControls
- let controls
- let BufferGeometryUtils
- let TextGeometry
- let fontLoad
- //Physics
- let RAPIER
- let physicsWorld
-
- let threeRenderer
- let scene
- let camera
- let eulerOrder = "YXZ"
-
- let composer
- let passes = {}
- let customEffects = []
- let renderTargets = {}
-
- let materials = {}
- let geometries = {}
- let lights = {}
- let models = {}
-
- let assets = { //should i place materials, geometries; inside too?
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {}, //not the same as the global one! this one only stores textures
- }
-
- let raycastResult = []
-
- function resetor(level) {
- camera = undefined
- composer.reset()
-
- passes = {}
- customEffects = []
- renderTargets = {}
-
- materials = {}
- geometries = {}
- lights = {}
- models = {}
-
- if (level > 0) {
- assets = {
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {},
- }
- }
-
- updateComposers()
- }
-
-//utility
- function vector3ToString(prop) {
- if (!prop) return "0,0,0";
-
- const x = (typeof(prop.x) === "number") ? prop.x : (typeof(prop._x) === "number") ? prop._x : (JSON.stringify(prop).includes("X")) ? prop : 0
- const y = (typeof(prop.y) === "number") ? prop.y : (typeof(prop.y) === "number") ? prop._y : 0
- const z = (typeof(prop.z) === "number") ? prop.z : (typeof(prop.z) === "number") ? prop.z : 0
-
- return [x, y, z]
- }
-
-//objects
- function createObject(name, content, parentName) {
- let object = getObject(name, true)
- if (object) {
- removeObject(name)
- alerts ? alert(name + " already exsisted, will replace!") : null
- }
- content.name = name
- content.rotation._order = eulerOrder
- parentName === scene.name ? object = scene : object = getObject(parentName)
- content.physics = false
-
- object.add(content)
- }
- function removeObject(name) {
- let object = getObject(name)
- if (!object) return
-
- scene.remove(object)
-
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true)
- physicsWorld.removeRigidBody(object.rigidBody, true)
- object.rigidBody = null
- object.collider = null
- }
- if (object.isLight) {
- delete(lights[name])
- }
- }
- function getObject(name, isNew) {
- let object = null
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null; return;}
- object = scene.getObjectByName(name)
- if (!object && !isNew) {alerts ? alert(name + " does not exist! Add it to scene"):null; return;}
- return object
- }
-
-//materials
- function encodeCostume (name) {
- if (name.startsWith("data:image/")) return name
- return Scratch.vm.editingTarget.sprite.costumes.find(c => c.name === name).asset.encodeDataURI()
- }
- function setTexutre (texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace
-
- if (mode === "Pixelate") {
- texture.minFilter = THREE.NearestFilter;
- texture.magFilter = THREE.NearestFilter;
- } else { //Blur
- texture.minFilter = THREE.NearestMipmapLinearFilter
- texture.magFilter = THREE.NearestMipmapLinearFilter
- }
-
- if (style === "Repeat") {
- texture.wrapS = THREE.RepeatWrapping
- texture.wrapT = THREE.RepeatWrapping
- texture.repeat.set(x, y)
- }
-
- texture.generateMipmaps = true;
- }
- async function resizeImageToSquare(uri, size = 256) {
- return new Promise((resolve) => {
- const img = new Image()
- img.onload = () => {
- const canvas = document.createElement('canvas')
- canvas.width = size
- canvas.height = size
- const ctx = canvas.getContext('2d')
-
- // clear + draw image scaled to fit canvas
- ctx.clearRect(0, 0, size, size)
- ctx.drawImage(img, 0, 0, size, size)
-
- resolve(canvas.toDataURL()) // return normalized Data URI
- //delete canvas?
- };
- img.src = uri
- });
-}
-//light
-function updateShadowFrustum(light, focusPos) {
- if (light.type !== "DirectionalLight") return
-
- // Frustum Size - Increase this value to cover a larger area.
- const d = 50;
-
- // Update Orthographic Shadow Camera Frustum
- const shadowCamera = light.shadow.camera;
-
- // Set the width/height of the frustum
- shadowCamera.left = -d;
- shadowCamera.right = d;
- shadowCamera.top = d;
- shadowCamera.bottom = -d;
-
- // Determine ranges
- shadowCamera.near = 0.1
- shadowCamera.far = 500
-
- // Position the Light and its Target
- light.target.position.copy(focusPos);
- const direction = light.position.clone().sub(light.target.position).normalize();
- light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
-
- // Ensure matrices are updated.
- light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix()
- light.shadow.needsUpdate = true;
-}
-//composer
-function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
-
- // always recreate the RenderPass to point to the current scene/camera
- passes["Render"] = new RenderPass(scene, camera);
-
- // ensure composer has a RenderPass as the first pass
- const hasRender = composer.passes.some(p => p && p.scene);
- if (!hasRender) composer.addPass(passes["Render"]);
- else {
- // if composer already has one, replace it so it references current scene/camera
- const idx = composer.passes.findIndex(p => p && p.scene);
- composer.passes[idx] = passes["Render"];
- }
-}
-//utility
-function getMouseNDC(event) {
- // Use threeRenderer.domElement for correct offset
- const rect = threeRenderer.domElement.getBoundingClientRect();
- const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
- const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
- return [x, y];
-}
-function checkCanvasSize() {
- const { width, height } = canvas
- if (width !== lastWidth || height !== lastHeight) {
- lastWidth = width
- lastHeight = height
- resize()
- }
- requestAnimationFrame(checkCanvasSize) //rerun next frame
-}
-//physics
-function computeWorldBoundingBox(mesh) {
- // Create a Box3 in world coordinates
- const box = new THREE.Box3().setFromObject(mesh);
- const size = new THREE.Vector3();
- box.getSize(size);
- const center = new THREE.Vector3();
- box.getCenter(center);
- return { size, center };
-}
-function createCuboidCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
- const collider = RAPIER.ColliderDesc.cuboid(
- size.x / 2,
- size.y / 2,
- size.z / 2
- )
- return collider;
-}
-function createBallCollider(mesh) {
- const { size } = computeWorldBoundingBox(mesh);
- // radius = 1/2 of the largest verticie
- const radius = Math.max(size.x, size.y, size.z) / 2;
- const collider = RAPIER.ColliderDesc.ball(radius)
- return collider //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
-}
-function createConvexHullCollider(mesh) {
- mesh.updateWorldMatrix(true, false);
-
- const position = mesh.geometry.attributes.position;
- const vertices = [];
- const vertex = new THREE.Vector3();
-
- // Matrix for scale only
- const scaleMatrix = new THREE.Matrix4().makeScale(
- mesh.scale.x,
- mesh.scale.y,
- mesh.scale.z
- );
-
- for (let i = 0; i < position.count; i++) {
- vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
- vertices.push(vertex.x, vertex.y, vertex.z);
- }
-
- const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
- return collider;
-}
-function TriMesh(mesh) {
- // Get the positions array (from your geoPoints function)
-const positions = mesh.geometry.attributes.position.array;
-const numVertices = positions.length / 3;
-
-// Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
-const indices = Array.from({ length: numVertices }, (_, i) => i);
-
-const collider = RAPIER.ColliderDesc.trimesh(
- positions,
- new Uint32Array(indices)
-);
-
-return collider
-}
-function getModel(model, name) {
- const file = runtime.getTargetForStage().getSounds().find(c => c.name === model)
- if (!file) return
-
-return new Promise((resolve, reject) => {
- gltf.parse(
- file.asset.data.buffer,
- "",
- gltf => {
- const root = gltf.scene
- root.traverse(child => {
- if (child.isMesh) {
- child.castShadow = true
- child.receiveShadow = true
- }
- });
-
- const mixer = new THREE.AnimationMixer(root)
- const actions = {}
- gltf.animations.forEach(clip => {
- const act = mixer.clipAction(clip)
- act.clampWhenFinished = true
- actions[clip.name] = act
- });
-
- models[name] = { root, mixer, actions }
- resolve(root)
- },
- error => {
- console.error("Error parsing GLB model:", error)
- reject(error)
- }
- )})
-}
-async function openFileExplorer(format) {
- return new Promise((resolve) => {
- const input = document.createElement("input");
- input.type = "file"
- input.accept = format
- input.multiple = false
- input.onchange = () => {
- resolve(input.files)
- input.remove()
- };
- input.click();
- })
-}
-function getMeshesUsingTexture(scene, targetTexture) {
- const meshes = []
-
- scene.traverse(object => {
- if (object.material) {
- const materials = Array.isArray(object.material) ? object.material : [object.material]
- for (const material of materials) {
- if (material.map === targetTexture) {
- meshes.push(object)
- break
- }
- }
- }
- })
-
- return meshes
-}
-function getAsset(path) {
- if (typeof(path) == "string") { //string?
- if (path.includes("/")) { //has the /?
- const value = path.split("/")
- console.log(value[0], value[1])
- return assets[value[0]][value[1]]
- }
- }
-
- return JSON.parse(path) //boolean or number
-}
-
-let mouseNDC = [0, 0]
-//loops/init
-function stopLoop() {
- if (!running) return
- running = false
-
- if (loopId) {
- cancelAnimationFrame(loopId)
- loopId = null
- if (threeRenderer) threeRenderer.clear();
- }
-}
-async function load() {
- if (!THREE) {
-
- // @ts-ignore
- THREE = await import("https://esm.sh/three@0.180.0")
- window._THREE_ = THREE
- //Addons
- GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js")
- OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js")
- BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js")
- TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js")
- const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js")
- fontLoad = new FontLoader.FontLoader()
-
- const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8")
- const {
- EffectComposer,
- EffectPass,
- RenderPass,
-
- Effect,
- BloomEffect,
- GodRaysEffect,
- DotScreenEffect,
- DepthOfFieldEffect,
-
- BlendFunction
- } = POSTPROCESSING
- //so i can use them later as global
- window.EffectComposer = EffectComposer;
- window.EffectPass = EffectPass;
- window.RenderPass = RenderPass;
- window.Effect = Effect;
- window.BloomEffect = BloomEffect;
- window.GodRaysEffect = GodRaysEffect;
- window.DotScreenEffect = DotScreenEffect;
- window.DepthOfFieldEffect = DepthOfFieldEffect;
- window.BlendFunction = BlendFunction;
-
- RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0")
- await RAPIER.init()
-
- threeRenderer = new THREE.WebGLRenderer({
- powerPreference: "high-performance",
- antialias: false,
- stencil: false,
- depth: true
- })
- threeRenderer.setPixelRatio(window.devicePixelRatio)
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping // HDR look (test)
- //threeRenderer.toneMappingExposure = 1.0 //(test)
-
- threeRenderer.shadowMap.enabled = true
- threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap // (optional)
- threeRenderer.domElement.style.pointerEvents = 'auto' //will disable turbowarp mouse events, but enable threejs's
-
- gltf = new GLTFLoader.GLTFLoader()
- clock = new THREE.Clock()
-
- // Example: create a composer
- composer = new EffectComposer(threeRenderer, {frameBufferType: THREE.HalfFloatType})
-
- renderer.addOverlay( threeRenderer.domElement, "manual" )
- renderer.addOverlay(canvas, "manual")
- renderer.setBackgroundColor(1, 1, 1, 0)
-
- resize()
-
- window.addEventListener("mousedown", e => {
- if (e.button === 0) isMouseDown.left = true
- if (e.button === 1) isMouseDown.middle = true
- if (e.button === 2) isMouseDown.right = true
- })
- window.addEventListener("mouseup", e => {
- if (e.button === 0) isMouseDown.left = false; prevMouse.left = false
- if (e.button === 1) isMouseDown.middle = false; prevMouse.middle = false
- if (e.button === 2) isMouseDown.right = false; prevMouse.right = false
- })
- // prevent contextmenu on right click
- threeRenderer.domElement.addEventListener("contextmenu", e => e.preventDefault());
-
- threeRenderer.domElement.addEventListener('mousemove', (event) => {
- mouseNDC = getMouseNDC(event);
- })
-
- running = false
- load()
-
- startRenderLoop()
- runtime.on('PROJECT_START', () => startRenderLoop())
- runtime.on('PROJECT_STOP_ALL', () => stopLoop())
- runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
- checkCanvasSize()
- }
- }
-function startRenderLoop() {
- if (running) return
- running = true
-
- const loop = () => {
- if (!running) return
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step()
-
- scene.children.forEach(obj => {
- if (!(obj.isMesh) || !(obj.physics)) return
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation())
- obj.quaternion.copy(obj.rigidBody.rotation())
- }
- })
-
- }
- if (scene && camera) {
- if (controls) controls.update()
-
- const delta = clock.getDelta()
- Object.values(models).forEach( model => { if (model) model.mixer.update(delta) } )
-
- Object.values(lights).forEach(light => updateShadowFrustum(light, camera.position))
-
- //update custom effects time
- customEffects.forEach(e => {
- if (e.uniforms.get('time')) {
- e.uniforms.get('time').value += delta
- }
- })
- Object.values(renderTargets).forEach(t => {
- if ( t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height
- t.camera.updateProjectionMatrix()
- }
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
-
- displayMeshes.forEach(mesh => {
- mesh.visible = false
- })
-
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target)
- threeRenderer.clear(true, true, true)
- threeRenderer.render(scene, t.camera)
- } else {
- t.target.clear(threeRenderer)
- t.camera.update( threeRenderer, scene ) //cubeCamera
- }
-
- displayMeshes.forEach(mesh => {
- mesh.visible = true
- })
- })
-
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height
- camera.updateProjectionMatrix()
- threeRenderer.setRenderTarget(null)
- composer.render(delta)
- }
-
- loopId = requestAnimationFrame(loop)
- }
-
- loopId = requestAnimationFrame(loop)
-}
-
-function resize() {
- const w = canvas.width
- const h = canvas.height
-
- threeRenderer.setSize(w, h)
- composer.setSize(w, h)
- customEffects.forEach(e => {
- if (e.uniforms.get('resolution')) {
- e.uniforms.get('resolution').value.set(w,h)
- }
- })
-
- if (camera) {
- camera.aspect = w / h
- camera.updateProjectionMatrix()
-}
-}
-//wait until all packages are loaded
-Promise.resolve(load()).then(() => {
-
- class threejsExtension {
- getInfo() {
- return {
- id: "threejsExtension",
- name: "Extra 3D",
- color1: "#222222",
- color2: "#222222",
- color3: "#11cc99",
- menuIconURI,
- blockIconURI: menuIconURI,
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Show Docs", func: "openDocs"},
- {blockType: Scratch.BlockType.BUTTON, text: "Toggle Alerts", func: "alerts"},
- ],
- menus: {}
- }}
- openDocs(){
- open("https://civ3ro.github.io/extensions/Documentation/")
- }
- alerts() {alerts = !alerts; alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!")}
- }
- Scratch.extensions.register(new threejsExtension())
-
- class ThreeRenderer {
- getInfo() {
- return {
- id: "threeRenderer",
- name: "Three Renderer",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setRendererRatio", blockType: Scratch.BlockType.COMMAND, text: "set Pixel Ratio to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"}}},
- {opcode: "eulerOrder", blockType: Scratch.BlockType.COMMAND, text: "set euler order to [VALUE]", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "YXZ"}}},
- ],
- menus: {}
- }}
-
- setRendererRatio(args) {
- threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE)
- }
- eulerOrder(args) {
- eulerOrder = args.VALUE
- console.log("euler order set to", eulerOrder)
- }
-
- }
- Scratch.extensions.register(new ThreeRenderer())
-
- class ThreeScene {
- constructor() {
- this.THREE = THREE;
- this.scenes = {};
- }
-
- getInfo() {
- return {
- id: "threeScene",
- name: "Three Scene",
- color1: "#4638c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newScene", blockType: Scratch.BlockType.COMMAND, text: "new Scene [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}}},
-
- {opcode: "setSceneProperty", blockType: Scratch.BlockType.COMMAND, text: "set Scene [PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "sceneProperties", defaultValue: "background"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
- "---",
- {opcode: "getSceneObjects", blockType: Scratch.BlockType.REPORTER, text: "get Scene [THING]", arguments:{THING: {type: Scratch.ArgumentType.STRING, menu: "sceneThings"}}},
- {opcode: "reset", blockType: Scratch.BlockType.COMMAND, text: "Reset Everything"}
- ],
- menus: {
- sceneProperties: {acceptReporters: false, items: [
- {text: "Background", value: "background"},{text: "Background Blurriness", value: "backgroundBlurriness"},{text: "Background Intensity", value: "backgroundIntensity"},{text: "Background Rotation", value: "backgroundRotation"},
- {text: "Environment", value: "environment"},{text: "Environment Intensity", value: "environmentIntensity"},{text: "Environment Rotation", value: "environmentRotation"},{text: "Fog", value: "fog"},
- ]},
- sceneThings: {acceptReporters: false, items: ["Objects", "Materials", "Geometries","Lights","Scene Properties","Other assets"]},
-
- }
- }}
-
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME
- scene.background = new THREE.Color("#222")
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {...this.scenes, {scene}};
- resetor(0)
- }
-
- reset() {
- resetor(1)
- }
-
- async setSceneProperty(args) {
- const property = args.PROPERTY;
- const value = getAsset(args.VALUE);
-
- scene[property] = value;
- }
- getSceneObjects(args){
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse(obj => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- }
- else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials))
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries))
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights))
- else if (args.THING === "Scene Properties") {console.log(scene); return "check console"}
- else if (args.THING === "Other assets") return JSON.stringify(assets)
-
- return JSON.stringify(names); // if objects
- }
-
- }
- Scratch.extensions.register(new ThreeScene())
-
- class ThreeCameras {
- getInfo() {
- return {
- id: "threeCameras",
- name: "Three Cameras",
- color1: "#38c59bff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addCamera", blockType: Scratch.BlockType.COMMAND, text: "add camera [TYPE] [CAMERA] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "cameraTypes"}}},
- {opcode: "setCamera", blockType: Scratch.BlockType.COMMAND, text: "set camera [PROPERTY] of [CAMERA] to [VALUE]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "0.1", exemptFromNormalization: true}}},
- {opcode: "getCamera", blockType: Scratch.BlockType.REPORTER, text: "get camera [PROPERTY] of [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "cameraProperties"}}},
- "---",
- {opcode: "renderSceneCamera", blockType: Scratch.BlockType.COMMAND, text: "set rendering camera to [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}}},
- "---",
- {opcode: "cubeCamera", blockType: Scratch.BlockType.COMMAND, text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "cubeCamera"}, GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
- "---",
- {opcode: "renderTarget", blockType: Scratch.BlockType.COMMAND, text: "set a RenderTarget: [RT] for camera [CAMERA]", arguments: {CAMERA: {type: Scratch.ArgumentType.STRING, defaultValue: "myCamera"}, RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, } },
- {opcode: "sizeTarget", blockType: Scratch.BlockType.COMMAND, text: "set RenderTarget [RT] size to [W] [H]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}, W: {type: Scratch.ArgumentType.NUMBER, defaultValue: 480}, H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 360},} },
- {opcode: "getTarget", blockType: Scratch.BlockType.REPORTER, text: "get RenderTarget: [RT] texture", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
- {opcode: "removeTarget", blockType: Scratch.BlockType.COMMAND, text: "remove RenderTarget: [RT]", arguments: {RT: {type: Scratch.ArgumentType.STRING, defaultValue: "myTarget"}} },
- ],
- menus: {
- cameraTypes: {acceptReporters: false, items: [
- {text: "Perspective", value: "PerspectiveCamera"},
- ]},
- cameraProperties: {acceptReporters: false, items: [
- {text: "Near", value: "near"},{text: "Far", value: "far"},{text: "FOV", value: "fov"},{text: "Focus (nothing...)", value: "focus"},{text: "Zoom", value: "zoom"},
- ]},
- }
- }}
- addCamera(args) {
- let v2 = new THREE.Vector2()
- threeRenderer.getSize(v2)
- const object = new THREE.PerspectiveCamera(90, v2.x / v2.y )
- object.position.z = 3
-
- createObject(args.CAMERA, object, args.GROUP)
- }
- setCamera(args) {
- let object = getObject(args.CAMERA)
- object[args.PROPERTY] = args.VALUE
- object.updateProjectionMatrix()
- }
- getCamera(args) {
- let object = getObject(args.CAMERA)
- const value = JSON.stringify(object[args.PROPERTY])
- return value
- }
- renderSceneCamera(args) {
- let object = getObject(args.CAMERA)
- if (!object) return
- camera = object
- //reset composer, else it does not update.
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
-
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true } )
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera( 0.1, 500, cubeRenderTarget )
- createObject(args.CAMERA, cubeCamera, args.GROUP)
-
- renderTargets[args.RT] = {target: cubeRenderTarget, camera: cubeCamera}
- assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture
- }
-
- renderTarget(args) {
- let object = getObject(args.CAMERA)
- const renderTarget = new THREE.WebGLRenderTarget(
- 360,
- 360,
- {
- generateMipmaps: false
- }
- )
-
- renderTargets[args.RT] = {target: renderTarget, camera: object}
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture
- }
- sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H)
- }
- getTarget(args) {
- const t = renderTargets[args.RT].target.texture
- console.log(t, renderTargets[args.RT])
- return `renderTargets/${t.uuid}`
- }
- removeTarget(args) {
- delete(assets.renderTargets[renderTargets[args.RT].target.texture.uuid])
- renderTargets[args.RT].target.dispose()
- delete(renderTargets[args.RT])
- }
- }
- Scratch.extensions.register(new ThreeCameras())
-
- class ThreeObjects {
- getInfo() {
- return {
- id: "threeObjects",
- name: "Three Objects",
- color1: "#38c567ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addObject", blockType: Scratch.BlockType.COMMAND, text: "add object [OBJECT3D] [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},TYPE: {type: Scratch.ArgumentType.STRING, menu: "objectTypes"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "cloneObject", blockType: Scratch.BlockType.COMMAND, text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myClone"},OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "setObject", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of object [OBJECT3D] to [NAME]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "getObject", blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of object [OBJECT3D]", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectProperties"}}},
- {opcode: "objectE", blockType: Scratch.BlockType.BOOLEAN, text: "is there an object [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "removeObject", blockType: Scratch.BlockType.COMMAND, text: "remove object [OBJECT3D] from scene", arguments: {OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: " ↳ Transforms"},
- {opcode: "setObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
- //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
- //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.REPORTER, text: "get [PROPERTY] of [OBJECT3D]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Materials"},
- {opcode: "newMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "new material [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "materialTypes", defaultValue: "MeshStandardMaterial"}}},
- {opcode: "materialE",extensions: ["colours_looks"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a material [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "removeMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "remove material [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}}},
- {opcode: "setMaterial",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [PROPERTY] of [NAME] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "materialProperties", defaultValue: "color"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "new Color()", exemptFromNormalization: true}}},
- {opcode: "setBlending",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] blending to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "blendModes"}}},
- {opcode: "setDepth",extensions: ["colours_looks"], blockType: Scratch.BlockType.COMMAND, text: "set material [NAME] depth to [VALUE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myMaterial"}, VALUE: {type: Scratch.ArgumentType.STRING, menu: "depthModes"}}},
-
- {blockType: Scratch.BlockType.LABEL, text: "↳ Geometries"},
- {opcode: "newGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new geometry [NAME] [TYPE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "geometryTypes", defaultValue: "BoxGeometry"}}},
- {opcode: "geometryE",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.BOOLEAN, text: "is there a geometry [NAME]?", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- {opcode: "removeGeometry",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "remove geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}}},
- "---",
- {opcode: "newGeo",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "new empty geometry [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoPoints",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] vertex points to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[points]"}}},
- {opcode: "geoUVs",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "set geometry [NAME] UVs to [POINTS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myGeometry"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[UVs]"}}},
- "---",
- {opcode: "splines", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create spline [NAME] from curve [CURVE]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}}},
- {opcode: "splineModel", extensions: ["colours_operators"], blockType: Scratch.BlockType.COMMAND, text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "mySpline"}, MODEL: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, CURVE: {type: Scratch.ArgumentType.STRING, defaultValue: "[curve]", exemptFromNormalization: true}, SPACING: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
- "---",
- {blockType: Scratch.BlockType.BUTTON, text: "Convert font to JSON", func: "openConv"},
- {blockType: Scratch.BlockType.BUTTON, text: "Load JSON font file", func: "loadFont"},
- {opcode: "text", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.COMMAND, text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myText"}, TEXT: {type: Scratch.ArgumentType.STRING, defaultValue: "C-369"}, FONT: {type: Scratch.ArgumentType.STRING, menu: "fonts"}, S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, D: {type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1}, CS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 6}}},
- ],
- menus: {
- objectVector3: {acceptReporters: false, items: [
- {text: "Positon", value: "position"},{text: "Rotation", value: "rotation"},{text: "Scale", value: "scale"},{text: "Facing Direction (.up)", value: "up"}
- ]},
- objectProperties: {acceptReporters: false, items: [
- {text: "Geometry", value: "geometry"},{text: "Material", value: "material"},{text: "Visible (true/false)", value: "visible"},
- ]},
- objectTypes: { acceptReporters: false, items: [
- { text: "Mesh", value: "Mesh" }, { text: "Sprite", value: "Sprite" }, { text: "Points", value: "Points" }, { text: "Line", value: "Line" }, { text: "Group", value: "Group" }
- ]},
- XYZ: {acceptReporters: false, items: [{text: "X", value: "x"},{text: "Y", value: "y"},{text: "Z", value: "z"}]},
- materialProperties: {acceptReporters: false, items: [
- "|GENERAL| <-- not a property",
- { text: "Color", value: "color" },
- { text: "Map", value: "map" },
- { text: "Opacity", value: "opacity" },
- { text: "Transparent", value: "transparent" },
- { text: "Alpha Map", value: "alphaMap" },
- { text: "Alpha Test", value: "alphaTest" },
- { text: "Depth Test", value: "depthTest" },
- { text: "Depth Write", value: "depthWrite" },
- { text: "Color Write", value: "colorWrite" },
- { text: "Side", value: "side" },
- { text: "Visible", value: "visible" },/*
- { text: "Blending", value: "blending" },
- { text: "Blend Src", value: "blendSrc" },
- { text: "Blend Dst", value: "blendDst" },
- { text: "Blend Equation", value: "blendEquation" },
- { text: "Blend Src Alpha", value: "blendSrcAlpha" },
- { text: "Blend Dst Alpha", value: "blendDstAlpha" },
- { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
- { text: "Blend Aplha", value: "blendAplha" },
- { text: "Blend Color", value: "blendColor" },
- { text: "Alpha Hash", value: "alphaHash" },
- { text: "Premultiplied Alpha", value: "premultipliedAlpha" },
-
- { text: "Tone Mapped", value: "toneMapped" },
- { text: "Fog", value: "fog" },
- { text: "Flat Shading", value: "flatShading" },
-
- "|MESH Standard / Physical| <-- not a property",
- { text: "Metalness", value: "metalness" },
- { text: "Metalness Map", value: "metalnessMap" },
- { text: "Roughness", value: "roughness" },
- { text: "Reflectivity", value: "reflectivity" },
- { text: "Roughness Map", value: "roughnessMap" },
- { text: "Emissive", value: "emissive" },
- { text: "Emissive Intensity", value: "emissiveIntensity" },
- { text: "Emissive Map", value: "emissiveMap" },
- { text: "Env Map", value: "envMap" },
- { text: "Env Map Intensity", value: "envMapIntensity" },
- { text: "Env Map Rotation", value: "envMapRotation" },
- { text: "Ior", value: "ior" },
- { text: "Refraction Ratio", value: "refractionRatio" },
- { text: "Clearcoat", value: "clearcoat" },
- { text: "Clearcoat Map", value: "clearcoatMap" },
- { text: "Clearcoat Roughness", value: "clearcoatRoughness" },
- { text: "Clearcoat Roughness Map", value: "clearcoatRoughnessMap" },
- { text: "Dispersion", value: "dispersion" },
- { text: "Sheen", value: "sheen" },
- { text: "Sheen Color", value: "sheenColor" },
- { text: "Sheen Color Map", value: "sheenColorMap" },
- { text: "Sheen Roughness", value: "sheenRoughness" },
- { text: "Sheen Roughness Map", value: "sheenRoughnessMap" },
- { text: "Specular Color", value: "specularColor" },
- { text: "Specular Color Map", value: "specularColorMap" },
- { text: "Specular Intensity", value: "specularIntensity" },
- { text: "Specular Intensity Map", value: "specularIntensityMap" },
- { text: "Transmission", value: "transmission" },
- { text: "Transmission Map", value: "transmissionMap" },
- { text: "Thickness", value: "thickness" },
- { text: "Thickness Map", value: "thicknessMap" },
- { text: "Anisotropy", value: "anisotropy" },
- { text: "Anisotropy Map", value: "anisotropyMap" },
- { text: "Anisotropy Rotation", value: "anisotropyRotation" },
- { text: "Attenuation Distance", value: "attenuationDistance" },
- { text: "Attenuation Color", value: "attenuationColor" },
- { text: "Thickness", value: "thickness" },
- { text: "Iridescence", value: "iridescence" },
- { text: "Iridescence Ior", value: "iridescenceIOR" },
- { text: "Iridescence Map", value: "iridescenceMap" },
- { text: "Iridescence Thickness Range", value: "iridescenceThicknessRange" },
-
- "|MESH Displacement / Normal / Bump| <-- not a property",
- { text: "Displacement Map", value: "displacementMap" },
- { text: "Displacement Scale", value: "displacementScale" },
- { text: "Displacement Bias", value: "displacementBias" },
- { text: "Bump Map", value: "bumpMap" },
- { text: "Bump Scale", value: "bumpScale" },
- { text: "Normal Map Type", value: "normalMapType" },
-
- "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
- { text: "Shininess", value: "shininess" },
-
- { text: "Wireframe", value: "wireframe" },
- { text: "Wireframe Linewidth", value: "wireframeLinewidth" },
- { text: "Wireframe Linecap", value: "wireframeLinecap" },
- { text: "Wireframe Linejoin", value: "wireframeLinejoin" },
-
- "|POINTS| <-- not a property",
- { text: "Size", value: "size" },
- { text: "Size Attenuation", value: "sizeAttenuation" },
-
- "|LINES| <-- not a property",
- { text: "Scale", value: "scale" },
- { text: "Dash Size", value: "dashSize" },
- { text: "Gap Size", value: "gapSize" },
-
- "|SPRITES| <-- not a property",
- { text: "Rotation", value: "rotation" }
-]},
- blendModes: {acceptReporters: false, items: [
- { text: "No Blending", value: "NoBlending" },{ text: "Normal Blending", value: "NormalBlending" },{ text: "Additive Blending", value: "AdditiveBlending" },{ text: "Subtractive Blending", value: "SubtractiveBlending" },{ text: "Multiply Blending", value: "MultiplyBlending" },{ text: "Custom Blending", value: "CustomBlending" }
- ]},
- depthModes: {acceptReporters: false, items: [
- { text: "Never Depth", value: "NeverDepth" },{ text: "Always Depth", value: "AlwaysDepth" },{ text: "Equal Depth", value: "EqualDepth" },{ text: "Less Depth", value: "LessDepth" },{ text: "Less Equal Depth", value: "LessEqualDepth" },{ text: "Greater Equal Depth", value: "GreaterEqualDepth" },{ text: "Greater Depth", value: "GreaterDepth" },{ text: "Not Equal Depth", value: "NotEqualDepth" }
- ]},
- materialTypes:{acceptReporters: false, items: [
- {text:"Mesh Basic Material",value:"MeshBasicMaterial"},{text:"Mesh Standard Material",value:"MeshStandardMaterial"},{text:"Mesh Physical Material",value:"MeshPhysicalMaterial"},{text:"Mesh Lambert Material",value:"MeshLambertMaterial"},{text:"Mesh Phong Material",value:"MeshPhongMaterial"},{text:"Mesh Depth Material",value:"MeshDepthMaterial"},{text:"Mesh Normal Material",value:"MeshNormalMaterial"},{text:"Mesh Matcap Material",value:"MeshMatcapMaterial"},{text:"Mesh Toon Material",value:"MeshToonMaterial"},{text:"Line Basic Material",value:"LineBasicMaterial"},{text:"Line Dashed Material",value:"LineDashedMaterial"},{text:"Points Material",value:"PointsMaterial"},{text:"Sprite Material",value:"SpriteMaterial"},{text:"Shadow Material",value:"ShadowMaterial"}
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- geometryTypes: {acceptReporters: false, items: [
- {text: "Box Geometry", value: "BoxGeometry"},{text: "Sphere Geometry", value: "SphereGeometry"},{text: "Cylinder Geometry", value: "CylinderGeometry"},{text: "Plane Geometry", value: "PlaneGeometry"},{text: "Circle Geometry", value: "CircleGeometry"},{text: "Torus Geometry", value: "TorusGeometry"},{text: "Torus Knot Geometry", value: "TorusKnotGeometry"},
- ]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model! (GLB Loader category)"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- fonts: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.json'))
- if (models.length < 1) return [["Load a font!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
-
- }
- }}
-
- addObject(args) {
- const object = new THREE[args.TYPE]();
-
- object.castShadow = true
- object.receiveShadow = true
-
- createObject(args.OBJECT3D, object, args.GROUP)
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D)
- const clone = object.clone(true)
- clone.name
- createObject(args.NAME, clone, args.GROUP)
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
-
- function degToRad(deg) {
- return deg * Math.PI / 180;
- }
-
-
- if (object.rigidBody) {
- const x = values[0]
- const y = values[1]
- const z = values[2]
- if (args.PROPERTY === "rotation") {
- const euler = new THREE.Euler(
- degToRad(x),
- degToRad(y),
- degToRad(z),
- 'YXZ'
- )
- const quaternion = new THREE.Quaternion()
- quaternion.setFromEuler(euler)
-
- object.rigidBody.setRotation({
- x: quaternion.x,
- y: quaternion.y,
- z: quaternion.z,
- w: quaternion.w
- });
- } else if (args.PROPERTY === "position") {
- object.rigidBody.setTranslation({ x: x, y: y, z: z }, true)
- }
- return
- }
-
- if (object.isCamera == true && controls) {
-
- }
-
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.set(0,0,0)
- }
- if (object.isDirectionalLight == true) {object.pos = new THREE.Vector3(...values); console.log(true, values, object.pos); return}
- object[args.PROPERTY].set(...values);
-
- if (object.type == "CubeCamera") object.updateCoordinateSystem()
- }
- /*
- changeObjectV3(args) {
- getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
-
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.x += values[0]
- object.rotation.y += values[1]
- object.rotation.z += values[2]
- }
- else {
- object[args.PROPERTY].add(...values);
- }
- }
- changeObjectXV3(args) {
- getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "rotation") value = value * Math.PI / 180
-
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let values = vector3ToString(object[args.PROPERTY])
- if (args.PROPERTY === "rotation") {
- const toDeg = Math.PI/180
- values = [values[0]/toDeg,values[1]/toDeg,values[2]/toDeg,]
- }
-
- return JSON.stringify(values)
- }
- setObject(args){
- let object = getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "material") {const mat = materials[args.NAME]; if (mat) value = mat; else value = undefined}
- else if (args.PROPERTY === "geometry") {const geo = geometries[args.NAME]; if (geo) value = geo; else value = undefined}
- else value = !!value
-
- if (value == undefined) return //invalid geo/mat
- object[args.PROPERTY] = value
- }
- getObject(args){
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let value
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value
- }
- removeObject(args) {
- removeObject(args.OBJECT3D)
- }
- objectE(args) {
- return scene.children.map(o => o.name).includes(args.NAME)
- }
-
-//defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert ("material already exists! will replace...")
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
-
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof(args.VALUE) == "string" && args.VALUE.at(0) == "|") return
- const mat = materials[args.NAME]
-
- let value = args.VALUE
-
- if (args.VALUE == "false") value = false
-
- if (args.PROPERTY == "side") {value = (args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide)}
- else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE))
- else value = getAsset(value)
-
-
- console.log("o:", args.VALUE, typeof(args.VALUE))
- console.log("r:", value, typeof(value))
-
- mat[args.PROPERTY] = await (value) //await incase its a texture
- mat.needsUpdate = true
- }
- setBlending(args) {
- const mat = materials[args.NAME]
- mat.blending = THREE[args.VALUE]
- mat.premultipliedAlpha = true
- mat.needsUpdate = true
- }
- setDepth(args) {
- const mat = materials[args.NAME]
- mat.depthFunc = THREE[args.VALUE]
- mat.needsUpdate = true
- }
- removeMaterial(args){
- const mat = materials[args.NAME]
- mat.dispose()
- delete(materials[args.NAME])
- }
- materialE(args) {
- return materials[args.NAME] ? true : false
- }
-
- newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert ("geometry already exists! will replace...")
- const geo = new THREE[args.TYPE]()
- geo.name = args.NAME
-
- geometries[args.NAME] = geo
- }
- setGeometry(args) {
- const geo = geometries[args.NAME]
- geo[args.PROPERTY] = (args.VALUE)
-
- geo.needsUpdate = true;
- }
- removeGeometry(args){
- const geo = geometries[args.NAME]
- geo.dispose()
- delete(geometries[args.NAME])
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false
- }
-
- newGeo(args) {
- const geometry = new THREE.BufferGeometry()
- geometry.name = args.NAME
- geometries[args.NAME] = geometry
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME]
- const positions = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v3 of each vertex of each triangle
-
- geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
- geometry.computeVertexNormals()
-
- geometry.needsUpdate = true
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME]
- const UVs = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v2 of each UV of each triangle
-
- geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(UVs), 2))
- geometry.needsUpdate = true
- }
-
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE))
- geometry.name = args.NAME
-
- geometries[args.NAME] = geometry
- }
-
- async splineModel(args) {
- const model = await getModel(args.MODEL, args.NAME)
- if (!model) return console.warn("Model not found:", args.MODEL)
-
- const curve = getAsset(args.CURVE)
- const spacing = parseFloat(args.SPACING) || 1
- const curveLength = curve.getLength()
- const divisions = Math.floor(curveLength / spacing)
-
- const geomList = []
- const matList = []
-
- for (let i = 0; i <= divisions; i++) {
- const t = i / divisions
- const pos = curve.getPointAt(t)
- const tangent = curve.getTangentAt(t)
-
- const temp = model.clone(true)
- temp.position.copy(pos)
-
- const up = new THREE.Vector3(0, 0, 1)
- const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize())
- temp.quaternion.copy(quat)
-
- temp.updateMatrixWorld(true)
-
- temp.traverse(child => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone()
- geom.applyMatrix4(child.matrixWorld)
- geomList.push(geom)
- matList.push(child.material) //.clone() ?
- }
- })
- }
-
- const validGeoms = geomList.filter(g => {
- const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position
- if (!ok) console.warn("geometry skipped:", g)
- return ok
- })
-
- const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true)
- merged.computeBoundingBox()
- merged.computeBoundingSphere()
-
- merged.name = args.NAME
- geometries[args.NAME] = merged
- matList.name = args.NAME
- materials[args.NAME] = matList
- }
-
- async text(args) {
- const fontFile = runtime.getTargetForStage().getSounds().find(c => c.name === args.FONT)
- if (!fontFile) return
-
- const json = new TextDecoder().decode(fontFile.asset.data.buffer)
- const fontData = JSON.parse(json)
-
- const font = fontLoad.parse(fontData)
-
- const params = {font: font, size: JSON.parse(args.S), height: JSON.parse(args.D), curveSegments: JSON.parse(args.CS), bevelEnabled: false}
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params)
- geometry.computeVertexNormals()
- geometry.center() // optional, recenters the text
-
-
- geometry.name = args.NAME
-
- geometries[args.NAME] = geometry
- }
-
- async loadFont() {
- openFileExplorer(".json").then(files => {
- const file = files[0]
- const reader = new FileReader()
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result
-
- // From lily's assets
- // // Thank you PenguinMod for providing this code.
-
- const targetId = runtime.getTargetForStage().id //util.target.id not working!
- const assetName = Cast.toString(file.name)
-
- const buffer = arrayBuffer
-
- const storage = runtime.storage
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- )
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- )
- alert("Font loaded successfully!")
- } catch (e) {
- console.error(e)
- alert("Error loading font.")
- }
-
- // End of PenguinMod
- }
-
- reader.readAsArrayBuffer(file);
- })
- }
- openConv() {{open("https://gero3.github.io/facetype.js/")}}
-
- }
- Scratch.extensions.register(new ThreeObjects())
-
- class ThreeLights {
- getInfo() {
- return {
- id: "threeLights",
- name: "Three Lights",
- color1: "#c7a22aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "addLight", blockType: Scratch.BlockType.COMMAND, text: "add light [NAME] type [TYPE] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, TYPE: {type: Scratch.ArgumentType.STRING, menu: "lightTypes"}}},
- {opcode: "setLight", blockType: Scratch.BlockType.COMMAND, text: "set light [NAME][PROPERTY] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lightProperties", defaultValue: "intensity"},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myLight"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1", exemptFromNormalization: true}}},
- ],
- menus: {
- lightTypes: {acceptReporters: false, items: [
- {text: "Ambient Light", value: "AmbientLight"},{text: "Directional Light", value: "DirectionalLight"},{text: "Point Light", value: "PointLight"},{text: "Hemisphere Light", value: "HemisphereLight"},{text: "Spot Light", value: "SpotLight"},
- ]},
- lightProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Intensity", value: "intensity"},{text: "Cast Shadow?", value: "castShadow"},
- {text: "Ground Color (HemisphereLight)", value: "groundColor"},
- {text: "Map (SpotLight)", value: "map"},{text: "Distance (SpotLight)", value: "distance"},{text: "Decay (SpotLight)", value: "decay"},{text: "Penumbra (SpotLight)", value: "penumbra"},{text: "Angle/Size (SpotLight)", value: "angle"},{text: "Power (SpotLight)", value: "power"},
- {text: "Target Position (Directional/SpotLight)", value: "target"},
- ]},
- }
- }}
-
- addLight(args) {
- const light = new THREE[args.TYPE](0xffffff, 1)
-
- createObject(args.NAME, light, args.GROUP)
- lights[args.NAME] = light
- if (light.type === "AmbientLight" || "HemisphereLight") return
-
- light.castShadow = true
- if (light.type === "PointLight") return
- //Directional & Spot Light
- light.target.position.set(0, 0, 0)
- scene.add(light.target)
-
- light.pos = new THREE.Vector3(0,0,0)
-
- light.shadow.mapSize.width = 4096
- light.shadow.mapSize.height = 2048
-
- if (light.type === "SpotLight") {
- light.decay = 0
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true
- light.needsUpdate = true
- }
-
- setLight(args) {
- const light = lights[args.NAME]
- if (!args.PROPERTY) return
- if (args.PROPERTY === "target") {
- light.target.position.set(...JSON.parse(args.VALUE)) //vector3
- light.target.updateMatrixWorld();
- }
- else {
- light[args.PROPERTY] = getAsset(args.VALUE)
- }
- light.needsUpdate = true
-
- if (light.type === "AmbientLight" || "HemisphereLight") return
-
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true
- }
-
- }
- Scratch.extensions.register(new ThreeLights())
-
- class ThreeUtilities {
- getInfo() {
- return {
- id: "threeUtility",
- name: "Three Utilities",
- color1: "#3875c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "newVector2", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}}},
- {opcode: "newVector3", blockType: Scratch.BlockType.REPORTER, text: "New Vector [X] [Y] [Z]", arguments: {X: {type: Scratch.ArgumentType.NUMBER}, Y: {type: Scratch.ArgumentType.NUMBER}, Z: {type: Scratch.ArgumentType.NUMBER}}},
- "---",
- {opcode: "operateV3", blockType: Scratch.BlockType.REPORTER, text: "do [V3] [O] [V32]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, O: {type: Scratch.ArgumentType.STRING, menu: "operators"}, V32: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "moveVector3", blockType: Scratch.BlockType.REPORTER, text: "move [S] steps in vector [V3] in direction [D3]", arguments: {S: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"}}},
- {opcode: "directionTo", blockType: Scratch.BlockType.REPORTER, text: "direction from [V3] to [T3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, T3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"}}},
- "---",
- {opcode: "newColor", blockType: Scratch.BlockType.REPORTER, text: "New Color [HEX]", arguments: {HEX: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff"}}},
- {opcode: "newFog", blockType: Scratch.BlockType.REPORTER, text: "New Fog [COLOR] [NEAR] [FAR]", arguments: {COLOR: {type: Scratch.ArgumentType.COLOR, defaultValue: "#9966ff", exemptFromNormalization: true}, NEAR: {type: Scratch.ArgumentType.NUMBER}, FAR: {type: Scratch.ArgumentType.NUMBER, defaultValue: 10}}},
- {opcode: "newTexture", blockType: Scratch.BlockType.REPORTER, text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newCubeTexture", blockType: Scratch.BlockType.REPORTER, text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]", arguments: {"COSTUMEX0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEX1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEY1": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ0": {type: Scratch.ArgumentType.COSTUME},"COSTUMEZ1": {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"},STYLE: {type: Scratch.ArgumentType.STRING, menu: "textureStyles"}, X: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1},Y: {type: Scratch.ArgumentType.NUMBER,defaultValue: 1}}},
- {opcode: "newEquirectangularTexture", blockType: Scratch.BlockType.REPORTER, text: "New Equirectangular Texture [COSTUME] [MODE]", arguments: {COSTUME: {type: Scratch.ArgumentType.COSTUME}, MODE: {type: Scratch.ArgumentType.STRING, menu: "textureModes"}}},
- "---",
- {opcode: "curve", extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]", arguments: {TYPE: {type: Scratch.ArgumentType.STRING, menu: "curveTypes"}, POINTS: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]"}, CLOSED: {type: Scratch.ArgumentType.STRING, defaultValue: "true"}}},
- "---",
- {opcode:"mouseDown",extensions: ["colours_sensing"], blockType: Scratch.BlockType.BOOLEAN, text: "mouse [BUTTON] [action]?", arguments: {BUTTON: {type: Scratch.ArgumentType.STRING, menu: "mouseButtons"},action: {type: Scratch.ArgumentType.STRING, menu: "mouseAction"}}},
- {opcode: "mousePos",extensions: ["colours_sensing"], blockType: Scratch.BlockType.REPORTER, text: "mouse position", arguments: {}},
- "---",
- {opcode: "getItem",extensions: ["colours_data_lists"], blockType: Scratch.BlockType.REPORTER, text: "get item [ITEM] of [ARRAY]", arguments: {ITEM: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}, ARRAY: {type: Scratch.ArgumentType.STRING, defaultValue: `["myObject", "myLight"]`}}},
- {blockType: Scratch.BlockType.LABEL, text: "↳ Raycasting"},
- {opcode: "raycast", blockType: Scratch.BlockType.COMMAND, text: "Raycast from [V3] in direction [D3]", arguments: {V3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,3]"}, D3: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,1]"}}},
- {opcode: "getRaycast", blockType: Scratch.BlockType.REPORTER, text: "get raycast [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "raycastProperties"}}},
-
- ],
- menus: {
- materialProperties: {acceptReporters: false, items: [
- {text: "Color", value: "color"},{text: "Map (texture)", value: "map"},{text: "Alpha Map (texture)", value: "alphaMap"},{text: "Alpha Test (0-1)", value: "alphaTest"},{text: "Side (front/back/double)", value: "side"},{text: "Bump Map (texture)", value: "bumpMap"},{text: "Bump Scale", value: "bumpScale"},
- ]},
- textureModes: {acceptReporters: false, items: ["Pixelate","Blur"]},
- textureStyles: {acceptReporters: false, items: ["Repeat","Clamp"]},
- raycastProperties: {acceptReporters: false, items: [
- {text: "Intersected Object Names", value: "name"},{text: "Number of Objects", value: "number"},{text: "Intersected Objects distances", value: "distance"},
- ]},
- mouseButtons: {acceptReporters: false, items: ["left","middle","right"]},
- mouseAction: {acceptReporters: false, items: ["Down","Clicked"]},
- curveTypes: {acceptReporters: false, items: ["CatmullRomCurve3"]},
- operators: {acceptReporters: false, items: [
- "+","-","*","/","=","max","min","dot","cross","distance to","angle to","apply euler",
- ]}
- }
- }}
- mouseDown(args) {
- if (args.action === "Down") return isMouseDown[args.BUTTON]
- if (args.action === "Clicked") {
- if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false
- else prevMouse[args.BUTTON] = true; return true
- }
- }
- mousePos(event) {
- return JSON.stringify(mouseNDC)
- }
- newVector3(args) {
- return JSON.stringify([args.X, args.Y, args.Z])
- }
- operateV3(args){
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const v32 = new THREE.Vector3(...JSON.parse(args.V32))
-
- let r
- if (args.O == "+") r = v3.add(v32)
- else if (args.O == "-") r = v3.sub(v32)
- else if (args.O == "*") r = v3.multiply(v32)
- else if (args.O == "/") r = v3.divide(v32)
- else if (args.O == "=") r = v3.equals(v32)
- else if (args.O == "max") r = v3.max(v32)
- else if (args.O == "min") r = v3.min(v32)
- else if (args.O == "dot") r = v3.dot(v32)
- else if (args.O == "cross") r = v3.cross(v32)
- else if (args.O == "distance to") r = v3.distanceTo(v32)
- else if (args.O == "angle to") r = v3.angleTo(v32)
- else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder))
-
- if (typeof(r) == "object") return JSON.stringify([r.x, r.y, r.z])
- else return JSON.stringify(r)
- }
-
- newVector2(args) {
- return JSON.stringify([args.X, args.Y])
- }
-
- moveVector3(args) {
- const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
- const steps = Number(args.S);
-
- const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
-
- const yaw = THREE.MathUtils.degToRad(yawInputDeg);
- const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
- const roll = THREE.MathUtils.degToRad(rollInputDeg);
-
- const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
-
- const forwardVector = new THREE.Vector3(0, 0, -1);
- const direction = forwardVector.applyEuler(euler).normalize();
-
- const newPos = currentPos.add(direction.multiplyScalar(steps));
- return JSON.stringify([newPos.x, newPos.y, newPos.z]);
- }
-
- directionTo(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3))
- const toV3 = new THREE.Vector3(...JSON.parse(args.T3))
-
- const direction = toV3.clone().sub(v3).normalize();
- // Pitch (X)
- const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x*direction.x + direction.z*direction.z));
- // Yaw (Y)
- const yaw = Math.atan2(direction.x, direction.z);
-
- // Roll always 0
- return JSON.stringify([180+THREE.MathUtils.radToDeg(pitch),THREE.MathUtils.radToDeg(yaw),0])
- }
-
- newColor(args) {
- const color = new THREE.Color(args.HEX)
- const uuid = crypto.randomUUID()
- assets.colors[uuid] = color
- return `colors/${uuid}`
- }
- newFog(args) {
- const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR)
- const uuid = crypto.randomUUID()
- assets.fogs[uuid] = fog
- return `fogs/${uuid}`
- }
- async newTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newCubeTexture(args) {
- const uris = [encodeCostume(args.COSTUMEX0),encodeCostume(args.COSTUMEX1), encodeCostume(args.COSTUMEY0),encodeCostume(args.COSTUMEY1), encodeCostume(args.COSTUMEZ0),encodeCostume(args.COSTUMEZ1)]
- const normalized = await Promise.all(uris.map(uri => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
- texture.mapping = THREE.EquirectangularReflectionMapping
-
- setTexutre(texture, args.MODE)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
-
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g)
- if (!matches) return []
-
- return matches.map(str => {
- const nums = str
- .replace(/[\[\]\s]/g, '')
- .split(',')
- .map(Number)
- return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0)
- })
- }
- const points = parsePoints(args.POINTS)
- const curve = new THREE[args.TYPE](points)
- curve.closed = JSON.parse(args.CLOSED)
-
- const uuid = crypto.randomUUID()
- assets.curves[uuid] = curve
- return `curves/${uuid}`
- }
-
- getItem(args) {
- const items = JSON.parse(args.ARRAY)
- const item = items[args.ITEM - 1]
- if (!item) return "0"
- return item
- }
-
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3))
- // rotation is in degrees => convert to radians first
- const rot = JSON.parse(args.D3).map(v => v * Math.PI / 180)
-
- const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder)
- const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize()
-
- const raycaster = new THREE.Raycaster()
- //const camera = getObject(args.CAMERA)
- raycaster.set( origin, direction );
-
- const intersects = raycaster.intersectObjects( scene.children, true )
-
- raycastResult = intersects
- }
- getRaycast(args) {
- if (args.PROPERTY === "number") return raycastResult.length
- if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map(i => i.distance))
- return JSON.stringify(raycastResult.map(i => i.object[args.PROPERTY]))
- }
-
- }
- Scratch.extensions.register(new ThreeUtilities())
-
- class ThreeGLB {
- getInfo() {
- return {
- id: "threeGLB",
- name: "Three GLB Loader",
- color1: "#c53838ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.BUTTON, text: "Load GLB File", func: "loadModelFile"},
- {opcode: "addModel", blockType: Scratch.BlockType.COMMAND, text: "add [ITEM] as [NAME] to [GROUP]", arguments: {GROUP: {type: Scratch.ArgumentType.STRING, defaultValue: "scene"},ITEM: {type: Scratch.ArgumentType.STRING, menu: "modelsList"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}}},
- {opcode: "getModel", blockType: Scratch.BlockType.REPORTER, text: "get object [PROPERTY] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "modelProperties"}}},
- {opcode: "playAnimation", blockType: Scratch.BlockType.COMMAND, text: "play animation [ANAME] of [NAME], [TIMES] times", arguments: {TIMES: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "pauseAnimation", blockType: Scratch.BlockType.COMMAND, text: "set [TOGGLE] animation [ANAME] of [NAME]", arguments: {TOGGLE: {type: Scratch.ArgumentType.NUMBER, menu: "pauseUn"}, NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
- {opcode: "stopAnimation", blockType: Scratch.BlockType.COMMAND, text: "stop animation [ANAME] of [NAME]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myModel"}, ANAME: {type: Scratch.ArgumentType.STRING, defaultValue: "walk",exemptFromNormalization: true}}},
-
- ],
- menus: {
- modelProperties: {acceptReporters: false, items: [
- {text: "Animations", value: "animations"},
- ]},
- pauseUn: {acceptReporters: true, items: [{text: "Pause", value: "true"},{text: "Unpasue", value: "false"},]},
- modelsList: {acceptReporters: false, items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime.getTargetForStage().getSounds().filter(e => e.name && e.name.endsWith('.glb'))
- if (models.length < 1) return [["Load a model!"]]
-
- // @ts-ignore
- return models.map( m => [m.name] )
- }},
- }
- }}
-
- async loadModelFile() {
-
- openFileExplorer(".glb").then(files => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- { // From lily's assets
-
- // Thank you PenguinMod for providing this code.
- {
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- //const res = await Scratch.fetch(args.URL);
- //const buffer = await res.arrayBuffer();
- const buffer = arrayBuffer
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Model loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading model.");
- }
- }
- // End of PenguinMod
- }
- };
-
- reader.readAsArrayBuffer(file);
- })
-
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME)
-
- createObject(args.NAME, group, args.GROUP)
- }
- getModel(args){
- if (!models[args.NAME]) return;
- return Object.keys(models[args.NAME].actions).toString()
- }
-
- playAnimation(args) {
- const model = models[args.NAME]
- if (!model) {console.log("no model!"); return}
-
- const action = model.actions[args.ANAME] //clones of models dont have a stored actions!
- if (!action) {
- console.log("no action!")
- return
- }
-
- args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity)
-
- action.reset()
- .play()
- }
- stopAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.stop();
- }
- pauseAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.paused = args.TOGGLE
- }
-
- }
- Scratch.extensions.register(new ThreeGLB())
-
- class ThreeAddons {
- getInfo() {
- return {
- id: "threeAddons",
- name: "Three Addons",
- color1: "#c538a2ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {blockType: Scratch.BlockType.LABEL, text: "Orbit Control"},
- {opcode: "OrbitControl", blockType: Scratch.BlockType.COMMAND, text: "set addon Orbit Control [STATE]", arguments: {STATE: {type: Scratch.ArgumentType.STRING, menu: "onoff"},}},
-
- {blockType: Scratch.BlockType.LABEL, text: "Post Processing"},
- {opcode: "resetComposer", blockType: Scratch.BlockType.COMMAND, text: "reset composer"},
- {opcode: "bloom", blockType: Scratch.BlockType.COMMAND, text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},I: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, T:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.5}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "godRays", blockType: Scratch.BlockType.COMMAND, text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}, DEC:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.95}, DENS:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},EXP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.1},WEI:{type: Scratch.ArgumentType.NUMBER, defaultValue: 0.4},RES:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1},SAMP:{type: Scratch.ArgumentType.NUMBER, defaultValue: 64},}},
- {opcode: "dots", blockType: Scratch.BlockType.COMMAND, text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]", arguments: {OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},S:{type: Scratch.ArgumentType.NUMBER, defaultValue: 1}, A: {type: Scratch.ArgumentType.ANGLE, defaultValue: 0},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "SCREEN"}}},
- {opcode: "depth", blockType: Scratch.BlockType.COMMAND, text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]", arguments: {FD: {type: Scratch.ArgumentType.NUMBER, defaultValue: (3)},FL: {type: Scratch.ArgumentType.NUMBER, defaultValue: (0.001)},BS: {type: Scratch.ArgumentType.NUMBER, defaultValue: 4},H: {type: Scratch.ArgumentType.NUMBER, defaultValue: 240},OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1},BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}}},
- "---",
- {opcode: "custom", blockType: Scratch.BlockType.COMMAND, text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]", arguments: {NAME: {type: Scratch.ArgumentType.STRING, defaultValue: "myShader"}, FRA: {type: Scratch.ArgumentType.STRING}, VER: {type: Scratch.ArgumentType.STRING}, BLEND: {type: Scratch.ArgumentType.STRING, menu: "blendModes", defaultValue: "NORMAL"}, OP: {type: Scratch.ArgumentType.NUMBER, defaultValue: 1}}},
- ],
- menus: {
- onoff: {acceptReporters: true, items: [{text: "enabled", value: "1"},{text: "disabled", value: "0"},]},
- blendModes: {acceptReporters: false, items: [
- "SKIP","SET","ADD","ALPHA","AVERAGE","COLOR","COLOR_BURN","COLOR_DODGE",
- "DARKEN","DIFFERENCE","DIVIDE","DST","EXCLUSION","HARD_LIGHT","HARD_MIX",
- "HUE","INVERT","INVERT_RGB","LIGHTEN","LINEAR_BURN","LINEAR_DODGE",
- "LINEAR_LIGHT","LUMINOSITY","MULTIPLY","NEGATION","NORMAL","OVERLAY",
- "PIN_LIGHT","REFLECT","SCREEN","SRC","SATURATION","SOFT_LIGHT","SUBTRACT",
- "VIVID_LIGHT"
- ]},
- }
- }}
-
- OrbitControl(args) {
- if (controls) controls.dispose()
-
- console.log("creating...", OrbitControls)
- controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
- controls.enableDamping = true
-
- controls.enabled = !!args.STATE
- console.log(controls)
- }
-
- resetComposer() {
- composer.passes = []
- passes = {}
- customEffects = []
- updateComposers()
- }
-
- bloom(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const bloomEffect = new BloomEffect({
- intensity: args.I,
- luminanceThreshold: args.T, // ← correct key
- luminanceSmoothing: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- bloomEffect.blendMode.opacity.value = args.OP
-
- const pass = new EffectPass(camera, bloomEffect)
-
- composer.addPass(pass)
- }
-
- godRays(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- let object = getObject(args.NAME)
- const sun = object
-
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- })
- godRays.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, godRays)
- composer.addPass(pass)
- }
-
- dots(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dot = new DotScreenEffect({
- angle: args.A,
- scale: args.S,
- blendFunction: BlendFunction[args.BLEND],
- })
- dot.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, dot)
- composer.addPass(pass)
- }
-
- depth(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- })
- dofEffect.blendMode.opacity.value = args.OP
-
- const dofPass = new EffectPass(camera, dofEffect)
- composer.addPass(dofPass)
- }
-
- async custom(args) {
- function cleanGLSL(glslCode) {
- //delete multilines comments
- let cleanedCode = glslCode.replace(/\/\*[\s\S]*?\*\//g, ' ')
- .replace(/ /g, '\n')
- .replace(/\/\/.*$/gm, ' ')
- .replace(/; /g, ';\n')
-
- return cleanedCode;
- }
-
- let fs = cleanGLSL(`
- ${args.FRA}
- `)
- if (!args.FRA.trim()) {fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`}
- const vs = cleanGLSL(`
- ${args.VER}
- `)
- console.log(fs)
- console.log(vs)
-
- const effect = new Effect(
- "Custom",
- fs,
- {
- blendFunction: BlendFunction[args.BLEND],
- vertexShader: vs,
- uniforms: new Map([ //uniforms usually in shaders... open to more!
- ['time', new THREE.Uniform(0.0)],
- ['resolution', new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height))]
- ]),
- defines: new Map([['USE_TIME', '1'], ['USE_VERTEX_TRANSFORM', '']]),
- }
- );
-
- effect.blendMode.opacity.value = args.OP
-
- const pass = new EffectPass(camera, effect);
- composer.addPass(pass);
-
- customEffects.push(effect);
- }
-
- }
- Scratch.extensions.register(new ThreeAddons())
-
- class RapierPhysics {
- getInfo() {
- return {
- id: "rapierPhysics",
- name: "RAPIER Physics",
- color1: "#222222",
- color2: "#203024ff",
- color3: "#78f07eff",
- blocks: [
- {opcode: "createWorld", blockType: Scratch.BlockType.COMMAND, text: "create world | gravity:[G]", arguments: {G: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,-9.81,0]"}}},
- {opcode: "getWorld", blockType: Scratch.BlockType.REPORTER, text: "get world [PROPERTY]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "wProp"}}},
- "---",
- {opcode: "objectPhysics", blockType: Scratch.BlockType.COMMAND, text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]", arguments: {state2: {type: Scratch.ArgumentType.STRING, menu: "state2"},state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"}, type: {type: Scratch.ArgumentType.STRING, menu: "objectTypes", defaultValue: "dynamic"}, collider: {type: Scratch.ArgumentType.STRING, menu: "colliderTypes", defaultValue: "cuboid"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},mass: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},density: {type: Scratch.ArgumentType.NUMBER, defaultValue: "1"},friction: {type: Scratch.ArgumentType.NUMBER, defaultValue: "0.5"}}},
- "---",
- {blockType: Scratch.BlockType.LABEL, text: "- RigidBody"},
- {opcode: "setRB", blockType: Scratch.BlockType.COMMAND, text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodySets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getRB", blockType: Scratch.BlockType.REPORTER, text: "get rigidbody [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "rigidBodyProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "lockObjectAxis", blockType: Scratch.BlockType.COMMAND, text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]", arguments: {OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "lockAxes"}, X: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Y: {type: Scratch.ArgumentType.STRING, menu: "tf"}, Z: {type: Scratch.ArgumentType.STRING, menu: "tf"}}},
- "---",
- {opcode: "addForce", blockType: Scratch.BlockType.COMMAND, text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space", arguments: {VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,10,0]"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "forces", defaultValue: "addForce"},SPACE: {type: Scratch.ArgumentType.STRING, menu: "spaces", defaultValue: "world"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "resetForces", blockType: Scratch.BlockType.COMMAND, text: "reset [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "resetF", defaultValue: "resetForces"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "enableCCD", blockType: Scratch.BlockType.COMMAND, text: "enable Continuous Collision Detection for [OBJECT] [state]", arguments: {state: {type: Scratch.ArgumentType.STRING, menu: "state", defaultValue: "true"},PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "oPropS", defaultValue: "physics"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "fixedJoint", blockType: Scratch.BlockType.COMMAND, text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},RA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},RB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},}},
- {opcode: "sphericalJoint", blockType: Scratch.BlockType.COMMAND, text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},}},
- {opcode: "revoluteJoint", blockType: Scratch.BlockType.COMMAND, text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]", arguments: {ObjA: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"},ObjB: {type: Scratch.ArgumentType.STRING, defaultValue: "myObjectB"},VA: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,0,0]"},VB: {type: Scratch.ArgumentType.STRING, defaultValue: "[0,1,0]"},X: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,0,0]"},}},
- "---",
- {blockType: Scratch.BlockType.LABEL, text: "- Collider"},
- {opcode: "setC", blockType: Scratch.BlockType.COMMAND, text: "set collider [PROPERTY] of [OBJECT] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderSets"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {opcode: "getC", blockType: Scratch.BlockType.REPORTER, text: "get collider [PROPERTY] of [OBJECT]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "colliderProperties"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- "---",
- {opcode: "sensorSingle", blockType: Scratch.BlockType.BOOLEAN, text: "is sensor [SENSOR] touching [OBJECT]?", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}, OBJECT: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}}},
- {opcode: "sensorAll", blockType: Scratch.BlockType.REPORTER, text: "objects touching sensor [SENSOR]", arguments: {SENSOR: {type: Scratch.ArgumentType.STRING, defaultValue: "mySensor"}}}
- ],
- menus: {
- wProp: {acceptReporters: false, items: [
- {text: "Gravity", value: "gravity"}, {text: "log to console", value: "log"}
- ]},
- tf: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true", value: "true"}]},
- lockAxes: {acceptReporters: false, items: [
- {text: "Translation", value: "setEnabledTranslations"}, {text: "Rotation", value: "setEnabledRotations"}
- ]},
- rigidBodyProperties: {acceptReporters: false, items: [
- {text: "Type", value: "bodyType"},
- {text: "Linear Velocity", value: "linvel"},
- {text: "Angular Velocity", value: "angvel"},
- {text: "Translation (position)", value: "translation"},
- {text: "Rotation (quaternion)", value: "rotation"},
- {text: "Mass", value: "mass"},
- //{text: "Center of Mass", value: "centerOfMass"},
- {text: "Linear Damping", value: "linearDamping"},
- {text: "Angular Damping", value: "angularDamping"},
- {text: "Is Sleeping?", value: "isSleeping"},
- //{text: "Can Sleep?", value: "isCanSleep"},
- {text: "Gravity Scale", value: "gravityScale"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"},
- //{text: "Sleeping", value: "sleeping"}
- ]},
- rigidBodySets: {acceptReporters: false, items: [
- //{text: "Linear Velocity", value: "setLinvel"},
- //{text: "Angular Velocity", value: "setAngvel"},
- //{text: "Mass", value: "setMass"},
- {text: "Gravity Scale", value: "setGravityScale"},
- //{text: "Can Sleep?", value: "setCanSleep"},
- //{text: "Sleeping", value: "sleeping"},
- {text: "Linear Damping", value: "setLinearDamping"},
- {text: "Angular Damping", value: "setAngularDamping"},
- {text: "Is Fixed?", value: "isFixed"},
- {text: "Is Dynamic?", value: "isDynamic"},
- {text: "Is Kinematic?", value: "isKinematic"}
- ]},
- colliderProperties: {acceptReporters: false, items: [
- //{text: "Collider Type", value: "type"},
- {text: "Is Sensor?", value: "isSensor"},
- {text: "Friction", value: "friction"},
- {text: "Restitution", value: "restitution"},
- {text: "Density", value: "density"},
- {text: "Mass", value: "mass"},
- {text: "Position", value: "translation"},
- {text: "Rotation", value: "rotation"},
- //{text: "Area", value: "area"},
- {text: "Volume", value: "volume"},
- {text: "Collision Groups", value: "collisionGroups"},
- //{text: "Collision Mask", value: "collisionMask"},
- //{text: "Is Enabled?", value: "enabled"},
- //{text: "Contact Count", value: "contactCount"},
- //{text: "RigidBody Handle", value: "rigidBody"}
- ]},
- colliderSets: {acceptReporters: false, items: [
- {text: "Friction", value: "setFriction"},
- {text: "Restitution", value: "setRestitution"},
- {text: "Density", value: "setDensity"},
- {text: "Is Sensor?", value: "setSensor"},
- {text: "Collision Groups", value: "setCollisionGroups"},
- //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
- //{text: "Position Offset", value: "setTranslation"},
- //{text: "Rotation Offset", value: "setRotation"}
- ]},
- state: {acceptReporters: true, items: [{text: "on", value: "true"},{text: "off", value: "false"}]},
- state2: {acceptReporters: true, items: [{text: "false", value: "false"},{text: "true (must be fixed)", value: "true"}]},
- spaces: {acceptReporters: false, items: [{text: "World", value: "world"},{text: "Local", value: "local"}]},
- objectTypes: {acceptReporters: false, items: [{text: "Dynamic", value: "dynamic"},{text: "Fixed", value: "fixed"},{text: "Kinematic Position Based",value: "kinematicPositionBased"}]},
- colliderTypes: {acceptReporters: false, items: [{text: "Box, Rectangle, cuboid", value: "cuboid"},{text: "Sphere, ball", value: "ball"},{text: "Custom, complex simple shapes, convexHull", value: "convexHull"},{text:"Precision, TriMesh",value:"trimesh"}]},
- forces: {acceptReporters: false, items: [{text: "Force", value: "addForce"},{text: "Torque (rotation)", value: "addTorque"},{text: "Apply Impulse", value: "applyImpulse"},{text: "Apply Torque Impulse (rotation)", value: "applyTorqueImpulse"},{text: "Linear Velocity", value: "setLinvel"},{text: "Angular Velocity", value: "setAngvel"},]},
- resetF: {acceptReporters: false, items: [{text:"Forces", value: "resetForces"},{text:"Torques", value: "resetTorques"},]}
- }
- }
- }
- joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true)
- }
-
- fixedJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- let RA = JSON.parse(args.RA).map(Number)
- let RB = JSON.parse(args.RB).map(Number)
-
- RA = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RA[0]),
- THREE.MathUtils.degToRad(RA[1]),
- THREE.MathUtils.degToRad(RA[2])
- )
- )
- RB = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RB[0]),
- THREE.MathUtils.degToRad(RB[1]),
- THREE.MathUtils.degToRad(RB[2])
- )
- )
-
- const data = RAPIER.JointData.fixed(
- { x: VA[0], y: VA[1], z: VA[2] }, RA,
- { x: VB[0], y: VB[1], z: VB[2] }, RB
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- sphericalJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
-
- const data = RAPIER.JointData.spherical(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- revoluteJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.revolute(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- prismaticJoint(args) {
- const VA = JSON.parse(args.VA).map(Number)
- const VB = JSON.parse(args.VB).map(Number)
- const x = JSON.parse(args.X).map(Number)
-
- const data = RAPIER.JointData.prismatic(
- { x: VA[0], y: VA[1], z: VA[2] },
- { x: VB[0], y: VB[1], z: VB[2] }, { x: x[0], y: x[1], z: x[2] },
- )
- const objectA = getObject(args.ObjA)
- let object = getObject(args.ObjB)
- this.joint(data, objectA, object)
- }
-
- createWorld(args) {
- const v3 = JSON.parse(args.G).map(Number)
- const gravity = { x: v3[0], y: v3[1], z: v3[2]}
- physicsWorld = new RAPIER.World(gravity)
-
- console.log(physicsWorld)
- }
-
- getWorld(args) {
- if (args.PROPERTY === "log") {console.log(physicsWorld); return "logged"}
- return JSON.stringify(physicsWorld[args.PROPERTY])
- }
-
- setRB(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.rigidBody[args.PROPERTY](value)
- }
- setC(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.collider[args.PROPERTY](value)
- }
-
- getRB(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.rigidBody[args.PROPERTY]())
- }
- getC(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.collider[args.PROPERTY]())
- }
-
- lockObjectAxis(args) {
- let object = getObject(args.OBJECT)
- const x = !JSON.parse(args.X)
- const y = !JSON.parse(args.Y)
- const z = !JSON.parse(args.Z)
- object.rigidBody[args.PROPERTY](x,y,z,true) //changes is xyz, wake up
- }
-
- objectPhysics(args) {
- let object = getObject(args.OBJECT)
- object.physics = JSON.parse(args.state)
-
- if (JSON.parse(args.state)) {
- //if already exists delete:
- if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
- }
- /*asing a rigidbody and collider to object and add them to physicsWorld*/
- let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
- .setTranslation(object.position.x, object.position.y, object.position.z)
- .setRotation({w: object.quaternion._w, x: object.quaternion._x, y: object.quaternion._y, z: object.quaternion._z})
-
- let colliderDesc
- switch(args.collider) {
- case "cuboid": colliderDesc = createCuboidCollider(object,); break
- case "ball": colliderDesc = createBallCollider(object); break
- case "convexHull": colliderDesc = createConvexHullCollider(object); break
- case "trimesh": colliderDesc = TriMesh(object); break
- }
- colliderDesc.setSensor(JSON.parse(args.state2)).setMass(args.mass).setDensity(args.density).setFriction(args.friction)
-
- let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc)
- let collider = physicsWorld.createCollider(colliderDesc, rigidBody)
-
- object.rigidBody = rigidBody
- object.collider = collider
- } else {
- /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
- }
-
- }
-
- enableCCD(args) {
- let object = getObject(args.OBJECT)
- if (object.physics) {
- let rigidBody = object.rigidBody
- rigidBody.enableCcd(JSON.parse(args.state))
- }
- }
-
- addForce(args) {
- let object = getObject(args.OBJECT)
- const vector = JSON.parse(args.VALUE).map(Number)
-
- let force = new THREE.Vector3(vector[0],vector[1],vector[2])
- if (args.SPACE === "local") {
- force.applyQuaternion(object.quaternion);
- }
-
- object.rigidBody[args.PROPERTY](force,true)
- }
-
- resetForces(args) {
- rigidBody[args.PROPERTY](true)
- }
-
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR)
-
- let object = getObject(args.OBJECT)
-
- let touching = false
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- if (otherCollider === object.collider) touching = true
- })
-
- return touching
- }
-
- sensorAll(args) {
- const sensor = getObject(args.SENSOR)
-
- const touchedObjects = []
-
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- // find owner of collider
- const otherObject = scene.children.find(o => o.collider === otherCollider)
- console.log(otherCollider)
- if (otherObject) touchedObjects.push(otherObject.name)
- })
-
- return JSON.stringify(touchedObjects)
- }
-
- }
- Scratch.extensions.register(new RapierPhysics())
-
- //Thanks to the PointerLock extension of Turbowarp
- const mouse = vm.runtime.ioDevices.mouse;
- let isLocked = false;
- let isPointerLockEnabled = false;
-
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
-
- const postMouseData = (e, isDown) => {
- const { movementX, movementY } = e;
- const { width, height } = rect;
- const x = mouse._clientX + movementX;
- const y = mouse._clientY - movementY;
- mouse._clientX = x;
- mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
- mouse._clientY = y;
- mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
- if (typeof isDown === "boolean") {
- const data = {
- button: e.button,
- isDown,
- };
- originalPostIOData(data);
- }
- };
-
- const mouseDevice = vm.runtime.ioDevices.mouse;
- const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
- mouseDevice.postData = (data) => {
- if (!isPointerLockEnabled) {
- return originalPostIOData(data);
- }
- };
-
- document.addEventListener(
- "mousedown",
- (e) => {
- // @ts-expect-error
- if (threeRenderer.domElement.contains(e.target)) {
- if (isLocked) {
- postMouseData(e, true);
- } else if (isPointerLockEnabled) {
- threeRenderer.domElement.requestPointerLock();
- }
- }
- },
- true
- );
- document.addEventListener(
- "mouseup",
- (e) => {
- if (isLocked) {
- postMouseData(e, false);
- // @ts-expect-error
- } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
- threeRenderer.domElement.requestPointerLock();
- }
- },
- true
- );
- document.addEventListener(
- "mousemove",
- (e) => {
- if (isLocked) {
- postMouseData(e);
- }
- },
- true
- );
-
- document.addEventListener("pointerlockchange", () => {
- isLocked = document.pointerLockElement === threeRenderer.domElement;
- });
- document.addEventListener("pointerlockerror", (e) => {
- console.error("Pointer lock error", e);
- });
-
- const oldStep = vm.runtime._step;
- vm.runtime._step = function (...args) {
- const ret = oldStep.call(this, ...args);
- if (isPointerLockEnabled) {
- const { width, height } = rect;
- mouse._clientX = width / 2;
- mouse._clientY = height / 2;
- mouse._scratchX = 0;
- mouse._scratchY = 0;
- }
- return ret;
- };
-
- vm.runtime.on("PROJECT_LOADED", () => {
- isPointerLockEnabled = false;
- if (isLocked) {
- document.exitPointerLock();
- }
- });
-
- class Pointerlock {
- getInfo() {
- return {
- id: "threepointerlockmod",
- name: "Pointerlock for Extra 3D",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [
- {opcode: "setLocked", blockType: Scratch.BlockType.COMMAND, text: "set pointer lock [enabled]", arguments: { enabled: { type: Scratch.ArgumentType.STRING, defaultValue: "true", menu: "enabled"}},},
- {opcode: "isLocked", blockType: Scratch.BlockType.BOOLEAN, text: "pointer locked?",},
- ],
- menus: {
- enabled: {acceptReporters: true, items: [
- {text: "enabled", value: "true"},{text: "disabled", value: "false"},
- ]}
- },
- }
- }
-
- setLocked(args) {
- isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
- if (!isPointerLockEnabled && isLocked) {
- document.exitPointerLock();
- }
- }
-
- isLocked() {
- return isLocked;
- }
- }
-Scratch.extensions.register(new Pointerlock())
-
- })
-
-
-
-
-})(Scratch);
diff --git a/threejsD_REMOTE_13852.js b/threejsD_REMOTE_13852.js
deleted file mode 100644
index 447584f..0000000
--- a/threejsD_REMOTE_13852.js
+++ /dev/null
@@ -1,5016 +0,0 @@
-/* jshint esversion: 11 */
-// Name: Extra 3D
-// ID: threejsExtension
-// Description: Use three js inside Turbowarp! A 3D graphics library.
-// By: Civero
-// License: MIT License Copyright (c) 2021-2024 TurboWarp Extensions Contributors
-
-(function(Scratch) {
- "use strict";
-
- if (!Scratch.extensions.unsandboxed) {
- throw new Error("Three-D extension must run unsandboxed");
- }
-
- if (Scratch.vm.runtime.isPackaged) {
- alert(`Uncheck the setting "Remove raw asset data after loading to save RAM" for package!`);
- return;
- }
- //if (Scratch.vm.extensionManager._loadedExtensions.has("threejsExtension") && typeof scaffolding == "undefined") return
-
- const vm = Scratch.vm;
- const runtime = vm.runtime;
- const renderer = Scratch.renderer;
- const canvas = renderer.canvas;
- const Cast = Scratch.Cast;
- const menuIconURI =
- "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwLDAsMTc3LjIzLDE4MC40NzU3MSIgaGVpZ2h0PSIxODAuNDc1NzEiIHdpZHRoPSIxNzcuMjMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2Ni4zODUsLTEwMS45OTQyOSkiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0iI2ZmZmZmZiIgZD0iTTMxMS4wMjY0NCwxMzYuMzI5ODRjLTAuMDgxMzYsMC4zNDU3OCAtMC4xNDIzOCwwLjY5MTU2IC0wLjI0NDA4LDEuMDM3MzRjLTAuMzA1MSwxLjI4MTQyIC0wLjkzNTY0LDQuMzEyMDggLTEuNTY2MTgsMTAuMjMxMDJjMCwwLjEwMTcgMCwwLjE4MzA2IC0wLjAyMDM0LDAuMjQ0MDhjMy40NzgxNCwxMy45OTM5MyAtMi4zNzk3OCwyMi41MTY0IC02LjI2NDcyLDI2LjQwMTM0Yy0wLjI0NDA4LDAuMjY0NDIgLTAuNTA4NSwwLjUwODUxIC0wLjc5MzI2LDAuNzUyNTljLTMuODAzNTgsMy40NTc4MSAtMTAuNDU0NzcsNy41ODY4MyAtMjAuMzgwNyw3LjU4NjgzYy00Ljk0MjYzLDAgLTkuNTU5OCwtMS4wOTgzNyAtMTMuNTg3MTMsLTMuMTEyMDNjMC4xMDE3LDUuNTUyODMgMC4xNjI3MiwxMy4yMDA2NyAwLjE2MjcyLDIzLjgxODE2YzMuNjYxMiwxLjI4MTQyIDcuMDE3MzEsMy4zNTYxMSA5Ljg2NDkxLDYuMDgxNjdjNS42NTQ1Miw1LjQzMDc5IDguNzQ2MiwxMi42OTIxNyA4Ljc0NjIsMjAuNDQxNzFjMCwxMS41MTI0NSAtNi42MzA4NCwyMS41MTk3MyAtMTcuMzA5MzUsMjYuMDk2MjRjLTAuMjY0NDIsMC4xMjIwNCAtMC41NDkxOSwwLjI0NDA4IC0wLjgxMzYsMC4zNDU3OGMtMy41Nzk4NCwxLjM2Mjc4IC03LjYwNzE2LDIuMDM0IC0xMi4zMjYwNSwyLjAzNGMtMS43MDg1NiwwIC0zLjUzOTE2LC0wLjA4MTM2IC01LjUzMjQ4LC0wLjI2NDQyYy0xLjIyMDQsLTAuMDYxMDIgLTMuMDEwMzIsLTAuMDQwNjggLTUuMTI1NjksMC4wMjAzNGMtMy44NDQyNywwLjQyNzE0IC05LjI1NDcxLDAuODU0MjggLTE2LjQ5NTc2LDEuMjYxMDhjLTAuMTQyMzgsMCAtMC4yODQ3NiwwLjAyMDM0IC0wLjQ0NzQ4LDAuMDIwMzRjLTAuOTU1OTgsMC4wNDA2OCAtMS44NzEyOCwwLjA2MTAyIC0yLjc2NjI0LDAuMDYxMDJjLTEyLjk1NjU5LDAgLTIyLjQxNDY5LC00LjEwODY5IC0yOC4xMzAyNCwtMTIuMTgzNjdjLTAuMTIyMDQsLTAuMTYyNzIgLTAuMjIzNzQsLTAuMzI1NDQgLTAuMzI1NDQsLTAuNDg4MTZjLTUuODE3MjQsLTguNjg1MTggLTUuOTc5OTYsLTE5LjY2ODc5IC0wLjQ0NzQ4LC0yOC42Mzg3NGMwLjA0MDY4LC0wLjEwMTcgMC4xMDE3LC0wLjE4MzA2IDAuMTYyNzIsLTAuMjg0NzZjMy41MTg4MiwtNS41MzI0OSA4LjY2NDg0LC05LjQ3ODQ1IDE1LjMzNjM3LC0xMS43OTcyMWMwLjA4MTM2LC0zLjkyNTYyIDAuMDYxMDIsLTguODQ3OSAtMC4wNjEwMiwtMTQuNjg1NDljLTMuMzE1NDMsMS4zODMxMiAtNy4xMzkzNCwyLjE5NjcyIC0xMS40MzEwOSwyLjE5NjcyYy0xMS4zMjkzOSwwIC0yMC42ODU4LC02LjczMjU0IC0yMy45NDAyLC0xNi45NjM1N2MtMC42NzEyMiwtMi4wNzQ2OCAtMS4zMDE3NiwtNS4xMDUzNCAtMi43NjYyNCwtMTEuOTM5NTljLTAuMDYxMDIsLTAuMjQ0MDggLTAuMTAxNywtMC40ODgxNiAtMC4xNDIzOCwtMC43MzIyNGwtMy4wMTAzMiwtMTYuODIxMTljLTAuMTAxNywtMC4zNjYxMiAtMC4yNDQwOCwtMC43OTMyNiAtMC40MDY4LC0xLjI4MTQyYy0xLjU2NjE4LC00LjQ1NDQ2IC0yLjI5ODQzLC04LjIzNzcxIC0yLjI5ODQzLC0xMS44OTg5MWMwLC00LjUzNTgyIDEuMzIyMSwtMTEuMzkwNCA3LjU4NjgzLC0xOC4yMjQ2NWMzLjE1MjcsLTMuNDU3OCA4Ljg4ODU5LC03LjkzMjYxIDE4LjEyMjk1LC05LjM3Njc1YzEuMTM5MDQsLTAuMTgzMDYgMi4yOTg0MywtMC4yODQ3NiAzLjQ1NzgxLC0wLjI4NDc2aDIyLjQ5NjA2YzAuNTA4NSwwIDEuMDE3LDAuMDIwMzQgMS41MjU1LDAuMDYxMDJjOC41ODM0OCwwLjMwNTEgMTYuMjcyMDEsMC4yODQ3NiAyMi44NjIxOCwtMC4wMjAzNGM5LjIxNDAyLC0wLjQwNjggMTguNDA3NzEsLTEuMjAwMDYgMjcuNDE4MzQsLTIuMzU5NDRjMS4wMTcsLTAuMzI1NDQgMi4xMTUzNiwtMC42NTA4OCAzLjI5NTA4LC0wLjkzNTY0YzEuMTE4NywtMC4yODQ3NiAyLjI1Nzc1LC0wLjQ2NzgyIDMuMzk2NzksLTAuNTg5ODZjOC42NjQ4NSwtMC43OTMyNiAxNi43Mzk4MywxLjcwODU2IDIzLjAyNDksNy4wNzgzMmM3Ljc5MDIzLDYuNjkxODYgMTEuMjI3NjksMTYuODIxMTkgOS4xNzMzNSwyNy4xMTMyNHoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjExLjU5OCwyODAuNDdsLTQzLjIxMywtMTc0Ljk0bDE3My4yMyw0OS44NzR6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTI1NC45NjgsMTMwLjQ3MmwyMS41OTEsODcuNDk2bC04Ni41NjcsLTI0Ljk0NXoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjMzLjQ4OCwyMDQuODlsLTEwLjcyNCwtNDMuNDY1bDQzLjAwOCwxMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIxMi4wMzYsMTE4LjAxM2wxMC43MjQsNDMuNDY1bC00My4wMDgsLTEyLjM0NnoiPjwvcGF0aD48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMjIyMjIyIiBkPSJNMjk4LjA0OCwxNDIuNzlsMTAuNzI0LDQzLjQ2NWwtNDMuMDA4LC0xMi4zNDZ6Ij48L3BhdGg+PHBhdGggc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iIzIyMjIyMiIgZD0iTTIzMy40OTMsMjA0LjkybDEwLjcyNCw0My40NjVsLTQzLjAwOCwtMTIuMzQ2eiI+PC9wYXRoPjxwYXRoIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMmRmZmIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNmN2Y3ZjciIGQ9Ik0yODkuMDgzNjMsMTMxLjk0NDUzYy0wLjgzMzk0LDMuMzQxODcgLTEuNTQ5OTEsNy44NzE1OCAtMi4xNDU4NywxMy41OTczYy0wLjI0MjA1LDIuODYzODggLTAuMTI0MDcsNS4xODg3NCAwLjM1Nzk5LDYuOTc4NjZjMC44Mjk4OCwyLjk4Mzg4IDAuNzc0OTUsNC45NTI3OSAtMC4xNzksNS45MDQ3Yy0xLjMxMzk2LDEuMTkxOTIgLTMuMTAxODUsMS43ODc4OSAtNS4zNjc3MywxLjc4Nzg5Yy0yLjYyNTksMCAtNC4zNTg4NiwtMC43NzkwMiAtNS4xODg3NCwtMi4zNDExM2MtMC4xMjQwOCwtNC44MDQzMSAtMC4wNjMwNiwtOS4zMTE2NiAwLjE3ODk5LC0xMy41MTc5OGMwLjIzMzkxLC01LjE2NjM3IDAuMzU3OTksLTcuODY5NTUgMC4zNTc5OSwtOC4xMDk1N2MtMC4xMjQwOCwwIC0wLjMwMTAzLC0wLjEyMDAxIC0wLjUzNjk4LC0wLjM2MjA1Yy0xMS4wOTU0OCwwLjQ4MjA2IC0yMS41MzE5NCwxLjE5ODAzIC0zMS4zMTE0MiwyLjE1NDAxYy0wLjI0MDAxLDEuMTk4MDMgLTAuMjQwMDEsMy4xMDc5NiAwLDUuNzM1ODhjMC40NzM5MiwzLjcwNTk1IDAuNzE1OTYsNS44NTc5MiAwLjcxNTk2LDYuNDUxODVjLTAuNDc1OTUsMy43MDU5NSAtMC43MTU5Niw5LjIwMTgyIC0wLjcxNTk2LDE2LjQ5MzcyYzAuNDczOTIsMy4xMDc5NiAwLjcxNTk2LDE2LjQzNDczIDAuNzE1OTYsMzkuOTc4M3YxMy4wODg4YzAsMi4wMzE5NyAwLjI5NDkzLDMuNDY1OTQgMC44ODY4Myw0LjMwMTkxaDEwLjk4NTY0YzIuMDA3NTYsLTAuMjM3OTggMy42MzA2OSwwLjI0MDAxIDQuODczNDcsMS40MzE5NGMxLjIzODcsMS4xOTM5NiAxLjg1OTA4LDIuNjIzODYgMS44NTkwOCw0LjI5MTc0YzAsMi42MjE4MyAtMS4yNTA5MSw0LjQ3Mjc3IC0zLjc1NjgsNS41NDg3NmMtMS41NTE5NCwwLjU5MzkzIC00LjI5Mzc3LDAuNzE1OTcgLTguMjI5NTcsMC4zNTc5OWMtMS45MDc4OSwtMC4xMjIwNCAtNC43MTI3OSwtMC4xMjIwNCAtOC40MTA2LDBjLTMuMzM5ODMsMC40MTQ5NCAtOC43MDU1MiwwLjgzMTkxIC0xNi4xMDExNSwxLjI1MDkxYy02LjQ0MTY5LDAuMjM3OTggLTEwLjM3NzQ4LC0wLjY1Njk4IC0xMS44MDk0MSwtMi42ODI4NWMtMC45NTU5OCwtMS40Mjk5IC0wLjk1NTk4LC0yLjkyMjg2IDAsLTQuNDcyNzdjMS42Njc4OCwtMi42MjE4MyA2LjAyMjY3LC0zLjkzNTggMTMuMDYyMzUsLTMuOTM1OGMyLjUwMzg1LDAgNC4wNTE3NCwtMC4yMDc0NiA0LjY0OTczLC0wLjYyNDQzYzAuNTk1OTYsLTAuNDE2OTcgMC44OTQ5NiwtMS4yMjI0NCAwLjg5NDk2LC0yLjQxNDM2YzAsLTEuMDY5ODggMCwtMi4wODA3OCAwLC0zLjAzNDczYzAsLTEuNzgzODIgMCwtNC40MDM2MiAwLC03Ljg1NTMxYzAuMzU3OTksLTYuMDY3NDIgMC4zNTc5OSwtMTUuMTE2NyAwLC0yNy4xMzk2OWMtMC40Nzc5OSwtMTcuMjU4NTEgLTAuMjQwMDEsLTMyLjQzMjE1IDAuNzE1OTcsLTQ1LjUyNzA1Yy0wLjEyLC0wLjExNzk3IC0wLjI5OSwtMC4yOTY5NyAtMC41NDEwNCwtMC41MzY5OGMtNC4zMTgxOCwwLjI0MDAxIC0xMS4yNzQ0OCwwLjEyMDAxIC0yMC44Njg4NiwtMC4zNjAwMmMtMS4wODAwNSwwIC00Ljc0MTI1LDAuMjQyMDUgLTEwLjk3NTQ3LDAuNzE4MDFjMS41NDk5MSwxMC44NTU0NyAyLjUwMzg1LDE5LjAyNjA1IDIuODYxODQsMjQuNTExNzVjMCwwLjcxNTk3IC0wLjEyLDIuMzI2OSAtMC4zNTc5OCw0LjgzMDc1Yy0wLjEyLDEuNzg3ODggLTEuNjEwOTMsMi42ODI4NSAtNC40NzI3NywyLjY4Mjg1Yy0xLjU1MTk0LDAgLTIuNDQ0ODcsLTAuNTMyOSAtMi42ODI4NSwtMS41OTY2OWMtMC4xMiwtMC4yMzM5MSAtMC44MzU5NywtMy40MzEzNiAtMi4xNDc5LC05LjU4MjE4Yy0wLjcxNTk3LC00LjAyMTIyIC0xLjczMDk0LC05LjcwMDE1IC0zLjA0MDg0LC0xNy4wMzQ3NmMwLC0wLjQ2NzgyIC0wLjQxOSwtMS45NDY1NCAtMS4yNTA5MSwtNC40MzQxMmMtMC43MTE5LC0yLjAxMTYzIC0xLjA2Nzg1LC0zLjU0OTMzIC0xLjA2Nzg1LC00LjYxMzExYzAsLTAuODI1ODEgMC41NjM0MiwtMS44NjUxOCAxLjcwMDQyLC0zLjEwMzg5YzEuMTMyOTQsLTEuMjQwNzQgMi44MzMzNiwtMi4wNDIxMyA1LjA5OTI0LC0yLjM5ODA4YzAuMzU3OTksMCAwLjkyMTQsMCAxLjcwMDQyLDBjMC43NzI5MiwwIDEuMzk5MzksMCAxLjg3NzM5LDBjMTQuMTk1MjksMCAyMC4zOTY5NiwwIDE4LjYwNzA1LDBjOS42NjE1MSwwLjM2MDAyIDE4LjI0OTA3LDAuMzYwMDIgMjUuNzYyNjcsMGMxMC43MzM0MywtMC40NzM5MiAyMS4zNDY4NSwtMS40Mjk5MSAzMS44NDYzNiwtMi44NjE4NGMwLjcxNTk3LC0wLjM1Nzk4IDEuNzg3ODgsLTAuNzE1OTcgMy4yMTc3OSwtMS4wNzE5MmMyLjYyNTksLTAuMjM3OTggNC43NzE3NywwLjM1Nzk4IDYuNDQzNzIsMS43ODk5MmMxLjY2Nzg4LDEuNDI3ODcgMi4yNjM4NCwzLjMzNzggMS43ODc4OCw1LjcyMzY4eiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjczLjYxNTAwMDAwMDAwMDAxOjc4LjAwNTcxMTMwMDg0OTk0LS0+";
-
- let alerts = false;
- console.log("alerts are " + (alerts ? "enabled" : "disabled"));
-
- let isMouseDown = {
- left: false,
- middle: false,
- right: false,
- };
- let prevMouse = {
- left: false,
- middle: false,
- right: false,
- };
-
- let lastWidth = 0;
- let lastHeight = 0;
-
- let THREE;
- let clock;
- let running;
- let loopId;
- //Addons
- let GLTFLoader;
- let gltf;
- let OrbitControls;
- let controls;
- let BufferGeometryUtils;
- let TextGeometry;
- let fontLoad;
- //Physics
- let RAPIER;
- let physicsWorld;
-
- let threeRenderer;
- let scene;
- let camera;
- let eulerOrder = "YXZ";
-
- let composer;
- let passes = {};
- let customEffects = [];
- let renderTargets = {};
-
- let materials = {};
- let geometries = {};
- let lights = {};
- let models = {};
-
- let assets = {
- //should i place materials, geometries; inside too?
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {}, //not the same as the global one! this one only stores textures
- };
-
- let raycastResult = [];
-
- function resetor(level) {
- camera = undefined;
- composer.reset();
-
- passes = {};
- customEffects = [];
- renderTargets = {};
-
- materials = {};
- geometries = {};
- lights = {};
- models = {};
-
- if (level > 0) {
- assets = {
- textures: {},
- colors: {},
- fogs: {},
- curves: {},
- renderTargets: {},
- };
- }
-
- updateComposers();
- }
-
- //utility
- function vector3ToString(prop) {
- if (!prop) return "0,0,0";
-
- const x =
- typeof prop.x === "number" ?
- prop.x :
- typeof prop._x === "number" ?
- prop._x :
- JSON.stringify(prop).includes("X") ?
- prop :
- 0;
- const y = typeof prop.y === "number" ? prop.y : typeof prop.y === "number" ? prop._y : 0;
- const z = typeof prop.z === "number" ? prop.z : typeof prop.z === "number" ? prop.z : 0;
-
- return [x, y, z];
- }
-
- //objects
- function createObject(name, content, parentName) {
- let object = getObject(name, true);
- if (object) {
- removeObject(name);
- alerts ? alert(name + " already exsisted, will replace!") : null;
- }
- content.name = name;
- content.rotation._order = eulerOrder;
- parentName === scene.name ? (object = scene) : (object = getObject(parentName));
- content.physics = false;
-
- object.add(content);
- }
-
- function removeObject(name) {
- let object = getObject(name);
- if (!object) return;
-
- scene.remove(object);
-
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true);
- physicsWorld.removeRigidBody(object.rigidBody, true);
- object.rigidBody = null;
- object.collider = null;
- }
- if (object.isLight) {
- delete lights[name];
- }
- }
-
- function getObject(name, isNew) {
- let object = null;
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
- return;
- }
- object = scene.getObjectByName(name);
- if (!object && !isNew) {
- alerts ? alert(name + " does not exist! Add it to scene") : null;
- return;
- }
- return object;
- }
-
- //materials
- function encodeCostume(name) {
- if (name.startsWith("data:image/")) return name;
- return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
- }
-
- function setTexutre(texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace;
-
- if (mode === "Pixelate") {
- texture.minFilter = THREE.NearestFilter;
- texture.magFilter = THREE.NearestFilter;
- } else {
- //Blur
- texture.minFilter = THREE.NearestMipmapLinearFilter;
- texture.magFilter = THREE.NearestMipmapLinearFilter;
- }
-
- if (style === "Repeat") {
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- texture.repeat.set(x, y);
- }
-
- texture.generateMipmaps = true;
- }
- async function resizeImageToSquare(uri, size = 256) {
- return new Promise((resolve) => {
- const img = new Image();
- img.onload = () => {
- const canvas = document.createElement("canvas");
- canvas.width = size;
- canvas.height = size;
- const ctx = canvas.getContext("2d");
-
- // clear + draw image scaled to fit canvas
- ctx.clearRect(0, 0, size, size);
- ctx.drawImage(img, 0, 0, size, size);
-
- resolve(canvas.toDataURL()); // return normalized Data URI
- //delete canvas?
- };
- img.src = uri;
- });
- }
- //light
- function updateShadowFrustum(light, focusPos) {
- if (light.type !== "DirectionalLight") return;
-
- // Frustum Size - Increase this value to cover a larger area.
- const d = 50;
-
- // Update Orthographic Shadow Camera Frustum
- const shadowCamera = light.shadow.camera;
-
- // Set the width/height of the frustum
- shadowCamera.left = -d;
- shadowCamera.right = d;
- shadowCamera.top = d;
- shadowCamera.bottom = -d;
-
- // Determine ranges
- shadowCamera.near = 0.1;
- shadowCamera.far = 500;
-
- // Position the Light and its Target
- light.target.position.copy(focusPos);
- const direction = light.position.clone().sub(light.target.position).normalize();
- light.position.copy(focusPos.clone().add(direction.multiplyScalar(100)));
-
- // Ensure matrices are updated.
- light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true;
- }
- //composer
- function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
-
- // always recreate the RenderPass to point to the current scene/camera
- passes["Render"] = new RenderPass(scene, camera);
-
- // ensure composer has a RenderPass as the first pass
- const hasRender = composer.passes.some((p) => p && p.scene);
- if (!hasRender) composer.addPass(passes["Render"]);
- else {
- // if composer already has one, replace it so it references current scene/camera
- const idx = composer.passes.findIndex((p) => p && p.scene);
- composer.passes[idx] = passes["Render"];
- }
- }
- //utility
- function getMouseNDC(event) {
- // Use threeRenderer.domElement for correct offset
- const rect = threeRenderer.domElement.getBoundingClientRect();
- const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
- const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
- return [x, y];
- }
-
- function checkCanvasSize() {
- const {
- width,
- height
- } = canvas;
- if (width !== lastWidth || height !== lastHeight) {
- lastWidth = width;
- lastHeight = height;
- resize();
- }
- requestAnimationFrame(checkCanvasSize); //rerun next frame
- }
- //physics
- function computeWorldBoundingBox(mesh) {
- // Create a Box3 in world coordinates
- const box = new THREE.Box3().setFromObject(mesh);
- const size = new THREE.Vector3();
- box.getSize(size);
- const center = new THREE.Vector3();
- box.getCenter(center);
- return {
- size,
- center,
- };
- }
-
- function createCuboidCollider(mesh) {
- const {
- size
- } = computeWorldBoundingBox(mesh);
- const collider = RAPIER.ColliderDesc.cuboid(size.x / 2, size.y / 2, size.z / 2);
- return collider;
- }
-
- function createBallCollider(mesh) {
- const {
- size
- } = computeWorldBoundingBox(mesh);
- // radius = 1/2 of the largest verticie
- const radius = Math.max(size.x, size.y, size.z) / 2;
- const collider = RAPIER.ColliderDesc.ball(radius);
- return collider; //there's a flaw with this, if the ball is deformed in just one axis it will be inaccurate. use convex instead (?)
- }
-
- function createConvexHullCollider(mesh) {
- mesh.updateWorldMatrix(true, false);
-
- const position = mesh.geometry.attributes.position;
- const vertices = [];
- const vertex = new THREE.Vector3();
-
- // Matrix for scale only
- const scaleMatrix = new THREE.Matrix4().makeScale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
-
- for (let i = 0; i < position.count; i++) {
- vertex.fromBufferAttribute(position, i).applyMatrix4(scaleMatrix);
- vertices.push(vertex.x, vertex.y, vertex.z);
- }
-
- const collider = RAPIER.ColliderDesc.convexHull(Float32Array.from(vertices));
- return collider;
- }
-
- function TriMesh(mesh) {
- // Get the positions array (from your geoPoints function)
- const positions = mesh.geometry.attributes.position.array;
- const numVertices = positions.length / 3;
-
- // Create an index array: [0, 1, 2, 3, 4, 5, ..., numVertices - 1]
- const indices = Array.from({
- length: numVertices,
- },
- (_, i) => i
- );
-
- const collider = RAPIER.ColliderDesc.trimesh(positions, new Uint32Array(indices));
-
- return collider;
- }
-
- function getModel(model, name) {
- const file = runtime
- .getTargetForStage()
- .getSounds()
- .find((c) => c.name === model);
- if (!file) return;
-
- return new Promise((resolve, reject) => {
- gltf.parse(
- file.asset.data.buffer,
- "",
- (gltf) => {
- const root = gltf.scene;
- root.traverse((child) => {
- if (child.isMesh) {
- child.castShadow = true;
- child.receiveShadow = true;
- }
- });
-
- const mixer = new THREE.AnimationMixer(root);
- const actions = {};
- gltf.animations.forEach((clip) => {
- const act = mixer.clipAction(clip);
- act.clampWhenFinished = true;
- actions[clip.name] = act;
- });
-
- models[name] = {
- root,
- mixer,
- actions,
- };
- resolve(root);
- },
- (error) => {
- console.error("Error parsing GLB model:", error);
- reject(error);
- }
- );
- });
- }
- async function openFileExplorer(format) {
- return new Promise((resolve) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = format;
- input.multiple = false;
- input.onchange = () => {
- resolve(input.files);
- input.remove();
- };
- input.click();
- });
- }
-
- function getMeshesUsingTexture(scene, targetTexture) {
- const meshes = [];
-
- scene.traverse((object) => {
- if (object.material) {
- const materials = Array.isArray(object.material) ? object.material : [object.material];
- for (const material of materials) {
- if (material.map === targetTexture) {
- meshes.push(object);
- break;
- }
- }
- }
- });
-
- return meshes;
- }
-
- function getAsset(path) {
- if (typeof path == "string") {
- //string?
- if (path.includes("/")) {
- //has the /?
- const value = path.split("/");
- console.log(value[0], value[1]);
- return assets[value[0]][value[1]];
- }
- }
-
- return JSON.parse(path); //boolean or number
- }
-
- let mouseNDC = [0, 0];
- //loops/init
- function stopLoop() {
- if (!running) return;
- running = false;
-
- if (loopId) {
- cancelAnimationFrame(loopId);
- loopId = null;
- if (threeRenderer) threeRenderer.clear();
- }
- }
- async function load() {
- if (!THREE) {
- // @ts-ignore
- THREE = await import("https://esm.sh/three@0.180.0");
- //Addons
- GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
- OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
- BufferGeometryUtils = await import("https://esm.sh/three@0.180.0/examples/jsm/utils/BufferGeometryUtils.js");
- TextGeometry = await import("https://esm.sh/three@0.158.0/examples/jsm/geometries/TextGeometry.js");
- const FontLoader = await import("https://esm.sh/three@0.158.0/examples/jsm/loaders/FontLoader.js");
- fontLoad = new FontLoader.FontLoader();
-
- const POSTPROCESSING = await import("https://esm.sh/postprocessing@6.37.8");
- const {
- EffectComposer,
- EffectPass,
- RenderPass,
-
- Effect,
- BloomEffect,
- GodRaysEffect,
- DotScreenEffect,
- DepthOfFieldEffect,
-
- BlendFunction,
- } = POSTPROCESSING;
- //so i can use them later as global
- window.EffectComposer = EffectComposer;
- window.EffectPass = EffectPass;
- window.RenderPass = RenderPass;
- window.Effect = Effect;
- window.BloomEffect = BloomEffect;
- window.GodRaysEffect = GodRaysEffect;
- window.DotScreenEffect = DotScreenEffect;
- window.DepthOfFieldEffect = DepthOfFieldEffect;
- window.BlendFunction = BlendFunction;
-
- RAPIER = await import("https://esm.sh/@dimforge/rapier3d-compat@0.19.0");
- await RAPIER.init();
-
- threeRenderer = new THREE.WebGLRenderer({
- powerPreference: "high-performance",
- antialias: false,
- stencil: false,
- depth: true,
- });
- threeRenderer.setPixelRatio(window.devicePixelRatio);
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
- //threeRenderer.toneMappingExposure = 1.0 //(test)
-
- threeRenderer.shadowMap.enabled = true;
- threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // (optional)
- threeRenderer.domElement.style.pointerEvents = "auto"; //will disable turbowarp mouse events, but enable threejs's
-
- gltf = new GLTFLoader.GLTFLoader();
- clock = new THREE.Clock();
-
- // Example: create a composer
- composer = new EffectComposer(threeRenderer, {
- frameBufferType: THREE.HalfFloatType,
- });
-
- renderer.addOverlay(threeRenderer.domElement, "manual");
- renderer.addOverlay(canvas, "manual");
- renderer.setBackgroundColor(1, 1, 1, 0);
-
- resize();
-
- window.addEventListener("mousedown", (e) => {
- if (e.button === 0) isMouseDown.left = true;
- if (e.button === 1) isMouseDown.middle = true;
- if (e.button === 2) isMouseDown.right = true;
- });
- window.addEventListener("mouseup", (e) => {
- if (e.button === 0) isMouseDown.left = false;
- prevMouse.left = false;
- if (e.button === 1) isMouseDown.middle = false;
- prevMouse.middle = false;
- if (e.button === 2) isMouseDown.right = false;
- prevMouse.right = false;
- });
- // prevent contextmenu on right click
- threeRenderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault());
-
- threeRenderer.domElement.addEventListener("mousemove", (event) => {
- mouseNDC = getMouseNDC(event);
- });
-
- running = false;
- load();
-
- startRenderLoop();
- runtime.on("PROJECT_START", () => startRenderLoop());
- runtime.on("PROJECT_STOP_ALL", () => stopLoop());
- runtime.on("STAGE_SIZE_CHANGED", () => {
- requestAnimationFrame(() => resize());
- });
- //if (!runtime.isPackaged) checkCanvasSize() //only in editor
- }
- }
-
- function startRenderLoop() {
- if (running) return;
- running = true;
-
- const loop = () => {
- if (!running) return;
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step();
-
- scene.children.forEach((obj) => {
- if (!obj.isMesh || !obj.physics) return;
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation());
- obj.quaternion.copy(obj.rigidBody.rotation());
- }
- });
- }
- if (scene && camera) {
- if (controls) controls.update();
-
- const delta = clock.getDelta();
- Object.values(models).forEach((model) => {
- if (model) model.mixer.update(delta);
- });
-
- Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
-
- //update custom effects time
- customEffects.forEach((e) => {
- if (e.uniforms.get("time")) {
- e.uniforms.get("time").value += delta;
- }
- });
- Object.values(renderTargets).forEach((t) => {
- if (t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height;
- t.camera.updateProjectionMatrix();
- }
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = false;
- });
-
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target);
- threeRenderer.clear(true, true, true);
- threeRenderer.render(scene, t.camera);
- } else {
- t.target.clear(threeRenderer);
- t.camera.update(threeRenderer, scene); //cubeCamera
- }
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = true;
- });
- });
-
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
- camera.updateProjectionMatrix();
- threeRenderer.setRenderTarget(null);
- composer.render(delta);
- }
-
- loopId = requestAnimationFrame(loop);
- };
-
- loopId = requestAnimationFrame(loop);
- }
-
- function resize() {
- const w = canvas.width;
- const h = canvas.height;
-
- threeRenderer.setSize(w, h);
- composer.setSize(w, h);
- customEffects.forEach((e) => {
- if (e.uniforms.get("resolution")) {
- e.uniforms.get("resolution").value.set(w, h);
- }
- });
-
- if (camera) {
- camera.aspect = w / h;
- camera.updateProjectionMatrix();
- }
- }
- //wait until all packages are loaded
- Promise.resolve(load()).then(() => {
- class threejsExtension {
- getInfo() {
- return {
- id: "threejsExtension",
- name: "Extra 3D",
- color1: "#222222",
- color2: "#222222",
- color3: "#11cc99",
- menuIconURI,
- blockIconURI: menuIconURI,
-
- blocks: [{
- blockType: Scratch.BlockType.BUTTON,
- text: "Show Docs",
- func: "openDocs",
- },
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Toggle Alerts",
- func: "alerts",
- },
- ],
- menus: {},
- };
- }
- openDocs() {
- open("https://civ3ro.github.io/extensions/Documentation/");
- }
- alerts() {
- alerts = !alerts;
- alerts ? alert("Alerts have been enabled!") : alert("Alerts have been disabled!");
- }
- }
- Scratch.extensions.register(new threejsExtension());
-
- class ThreeRenderer {
- getInfo() {
- return {
- id: "threeRenderer",
- name: "Three Renderer",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "setRendererRatio",
- blockType: Scratch.BlockType.COMMAND,
- text: "set Pixel Ratio to [VALUE]",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "eulerOrder",
- blockType: Scratch.BlockType.COMMAND,
- text: "set euler order to [VALUE]",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "YXZ",
- },
- },
- },
- ],
- menus: {},
- };
- }
-
- setRendererRatio(args) {
- threeRenderer.setPixelRatio(window.devicePixelRatio * args.VALUE);
- }
- eulerOrder(args) {
- eulerOrder = args.VALUE;
- console.log("euler order set to", eulerOrder);
- }
- }
- Scratch.extensions.register(new ThreeRenderer());
-
- class ThreeScene {
- constructor() {
- this.THREE = THREE;
- this.scenes = {};
- }
-
- getInfo() {
- return {
- id: "threeScene",
- name: "Three Scene",
- color1: "#4638c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "newScene",
- blockType: Scratch.BlockType.COMMAND,
- text: "new Scene [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- },
- },
-
- {
- opcode: "setSceneProperty",
- blockType: Scratch.BlockType.COMMAND,
- text: "set Scene [PROPERTY] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "sceneProperties",
- defaultValue: "background",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "new Color()",
- exemptFromNormalization: true,
- },
- },
- },
- "---",
- {
- opcode: "getSceneObjects",
- blockType: Scratch.BlockType.REPORTER,
- text: "get Scene [THING]",
- arguments: {
- THING: {
- type: Scratch.ArgumentType.STRING,
- menu: "sceneThings",
- },
- },
- },
- {
- opcode: "reset",
- blockType: Scratch.BlockType.COMMAND,
- text: "Reset Everything",
- },
- ],
- menus: {
- sceneProperties: {
- acceptReporters: false,
- items: [{
- text: "Background",
- value: "background",
- },
- {
- text: "Background Blurriness",
- value: "backgroundBlurriness",
- },
- {
- text: "Background Intensity",
- value: "backgroundIntensity",
- },
- {
- text: "Background Rotation",
- value: "backgroundRotation",
- },
- {
- text: "Environment",
- value: "environment",
- },
- {
- text: "Environment Intensity",
- value: "environmentIntensity",
- },
- {
- text: "Environment Rotation",
- value: "environmentRotation",
- },
- {
- text: "Fog",
- value: "fog",
- },
- ],
- },
- sceneThings: {
- acceptReporters: false,
- items: ["Objects", "Materials", "Geometries", "Lights", "Scene Properties", "Other assets"],
- },
- },
- };
- }
-
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME;
- scene.background = new THREE.Color("#222");
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {
- ...this.scenes,
- ...scene,
- };
- resetor(0);
- }
-
- reset() {
- resetor(1);
- }
-
- async setSceneProperty(args) {
- const property = args.PROPERTY;
- const value = getAsset(args.VALUE);
-
- scene[property] = value;
- }
- getSceneObjects(args) {
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse((obj) => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
- else if (args.THING === "Scene Properties") {
- console.log(scene);
- return "check console";
- } else if (args.THING === "Other assets") return JSON.stringify(assets);
-
- return JSON.stringify(names); // if objects
- }
- }
- Scratch.extensions.register(new ThreeScene());
-
- class ThreeCameras {
- getInfo() {
- return {
- id: "threeCameras",
- name: "Three Cameras",
- color1: "#38c59bff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "add camera [TYPE] [CAMERA] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraTypes",
- },
- },
- },
- {
- opcode: "setCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "set camera [PROPERTY] of [CAMERA] to [VALUE]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraProperties",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "0.1",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "getCamera",
- blockType: Scratch.BlockType.REPORTER,
- text: "get camera [PROPERTY] of [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "cameraProperties",
- },
- },
- },
- "---",
- {
- opcode: "renderSceneCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "set rendering camera to [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- },
- },
- "---",
- {
- opcode: "cubeCamera",
- blockType: Scratch.BlockType.COMMAND,
- text: "add cube camera [CAMERA] to [GROUP] with RenderTarget [RT]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "cubeCamera",
- },
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- "---",
- {
- opcode: "renderTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "set a RenderTarget: [RT] for camera [CAMERA]",
- arguments: {
- CAMERA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myCamera",
- },
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- {
- opcode: "sizeTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "set RenderTarget [RT] size to [W] [H]",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- W: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 480,
- },
- H: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 360,
- },
- },
- },
- {
- opcode: "getTarget",
- blockType: Scratch.BlockType.REPORTER,
- text: "get RenderTarget: [RT] texture",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- {
- opcode: "removeTarget",
- blockType: Scratch.BlockType.COMMAND,
- text: "remove RenderTarget: [RT]",
- arguments: {
- RT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myTarget",
- },
- },
- },
- ],
- menus: {
- cameraTypes: {
- acceptReporters: false,
- items: [{
- text: "Perspective",
- value: "PerspectiveCamera",
- }, ],
- },
- cameraProperties: {
- acceptReporters: false,
- items: [{
- text: "Near",
- value: "near",
- },
- {
- text: "Far",
- value: "far",
- },
- {
- text: "FOV",
- value: "fov",
- },
- {
- text: "Focus (nothing...)",
- value: "focus",
- },
- {
- text: "Zoom",
- value: "zoom",
- },
- ],
- },
- },
- };
- }
- addCamera(args) {
- let v2 = new THREE.Vector2();
- threeRenderer.getSize(v2);
- const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
- object.position.z = 3;
-
- createObject(args.CAMERA, object, args.GROUP);
- }
- setCamera(args) {
- let object = getObject(args.CAMERA);
- object[args.PROPERTY] = args.VALUE;
- object.updateProjectionMatrix();
- }
- getCamera(args) {
- let object = getObject(args.CAMERA);
- const value = JSON.stringify(object[args.PROPERTY]);
- return value;
- }
- renderSceneCamera(args) {
- let object = getObject(args.CAMERA);
- if (!object) return;
- camera = object;
- //reset composer, else it does not update.
- composer.passes = [];
- passes = {};
- customEffects = [];
- updateComposers();
- }
-
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
- generateMipmaps: true,
- });
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera(0.1, 500, cubeRenderTarget);
- createObject(args.CAMERA, cubeCamera, args.GROUP);
-
- renderTargets[args.RT] = {
- target: cubeRenderTarget,
- camera: cubeCamera,
- };
- assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
- }
-
- renderTarget(args) {
- let object = getObject(args.CAMERA);
- const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
- generateMipmaps: false,
- });
-
- renderTargets[args.RT] = {
- target: renderTarget,
- camera: object,
- };
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
- }
- sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H);
- }
- getTarget(args) {
- const t = renderTargets[args.RT].target.texture;
- console.log(t, renderTargets[args.RT]);
- return `renderTargets/${t.uuid}`;
- }
- removeTarget(args) {
- delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
- renderTargets[args.RT].target.dispose();
- delete renderTargets[args.RT];
- }
- }
- Scratch.extensions.register(new ThreeCameras());
-
- class ThreeObjects {
- getInfo() {
- return {
- id: "threeObjects",
- name: "Three Objects",
- color1: "#38c567ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "add object [OBJECT3D] [TYPE] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectTypes",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "cloneObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "clone object [OBJECT3D] as [NAME] & add to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myClone",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "setObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [PROPERTY] of object [OBJECT3D] to [NAME]",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectProperties",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- {
- opcode: "getObject",
- blockType: Scratch.BlockType.REPORTER,
- text: "get [PROPERTY] of object [OBJECT3D]",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectProperties",
- },
- },
- },
- {
- opcode: "objectE",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there an object [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "removeObject",
- blockType: Scratch.BlockType.COMMAND,
- text: "remove object [OBJECT3D] from scene",
- arguments: {
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: " ↳ Transforms",
- },
- {
- opcode: "setObjectV3",
- extensions: ["colours_motion"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set transform [PROPERTY] of [OBJECT3D] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectVector3",
- defaultValue: "position",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- //{opcode: "changeObjectV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] of [OBJECT3D] by [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3", defaultValue: "position"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "[1,1,1]"}}},
- //{opcode: "changeObjectXV3",extensions: ["colours_motion"], blockType: Scratch.BlockType.COMMAND, text: "change transform [PROPERTY] [X] of [OBJECT3D] to [VALUE]", arguments: {PROPERTY: {type: Scratch.ArgumentType.STRING, menu: "objectVector3"},X: {type: Scratch.ArgumentType.STRING, menu: "XYZ"}, OBJECT3D: {type: Scratch.ArgumentType.STRING, defaultValue: "myObject"}, VALUE: {type: Scratch.ArgumentType.STRING, defaultValue: "1"}}},
- {
- opcode: "getObjectV3",
- extensions: ["colours_motion"],
- blockType: Scratch.BlockType.REPORTER,
- text: "get [PROPERTY] of [OBJECT3D]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectVector3",
- defaultValue: "position",
- },
- OBJECT3D: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Materials",
- },
- {
- opcode: "newMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new material [NAME] [TYPE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "materialTypes",
- defaultValue: "MeshStandardMaterial",
- },
- },
- },
- {
- opcode: "materialE",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there a material [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- },
- },
- {
- opcode: "removeMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "remove material [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- },
- },
- {
- opcode: "setMaterial",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [PROPERTY] of [NAME] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "materialProperties",
- defaultValue: "color",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "new Color()",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "setBlending",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [NAME] blending to [VALUE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- },
- },
- },
- {
- opcode: "setDepth",
- extensions: ["colours_looks"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set material [NAME] depth to [VALUE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myMaterial",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- menu: "depthModes",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Geometries",
- },
- {
- opcode: "newGeometry",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new geometry [NAME] [TYPE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "geometryTypes",
- defaultValue: "BoxGeometry",
- },
- },
- },
- {
- opcode: "geometryE",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is there a geometry [NAME]?",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- {
- opcode: "removeGeometry",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "remove geometry [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- },
- },
- "---",
- {
- opcode: "newGeo",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "new empty geometry [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[points]",
- },
- },
- },
- {
- opcode: "geoPoints",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set geometry [NAME] vertex points to [POINTS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[points]",
- },
- },
- },
- {
- opcode: "geoUVs",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "set geometry [NAME] UVs to [POINTS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myGeometry",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[UVs]",
- },
- },
- },
- "---",
- {
- opcode: "splines",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create spline [NAME] from curve [CURVE]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySpline",
- },
- CURVE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[curve]",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "splineModel",
- extensions: ["colours_operators"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create (geometry&material) spline [NAME] using model [MODEL] along curve [CURVE] with spacing [SPACING]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySpline",
- },
- MODEL: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelsList",
- },
- CURVE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[curve]",
- exemptFromNormalization: true,
- },
- SPACING: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Convert font to JSON",
- func: "openConv",
- },
- {
- blockType: Scratch.BlockType.BUTTON,
- text: "Load JSON font file",
- func: "loadFont",
- },
- {
- opcode: "text",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.COMMAND,
- text: "create text geometry [NAME] with text [TEXT] in font [FONT] size [S] depth [D] curvedSegments [CS]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myText",
- },
- TEXT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "C-369",
- },
- FONT: {
- type: Scratch.ArgumentType.STRING,
- menu: "fonts",
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- D: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.1,
- },
- CS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 6,
- },
- },
- },
- ],
- menus: {
- objectVector3: {
- acceptReporters: false,
- items: [{
- text: "Positon",
- value: "position",
- },
- {
- text: "Rotation",
- value: "rotation",
- },
- {
- text: "Scale",
- value: "scale",
- },
- {
- text: "Facing Direction (.up)",
- value: "up",
- },
- ],
- },
- objectProperties: {
- acceptReporters: false,
- items: [{
- text: "Geometry",
- value: "geometry",
- },
- {
- text: "Material",
- value: "material",
- },
- {
- text: "Visible (true/false)",
- value: "visible",
- },
- ],
- },
- objectTypes: {
- acceptReporters: false,
- items: [{
- text: "Mesh",
- value: "Mesh",
- },
- {
- text: "Sprite",
- value: "Sprite",
- },
- {
- text: "Points",
- value: "Points",
- },
- {
- text: "Line",
- value: "Line",
- },
- {
- text: "Group",
- value: "Group",
- },
- ],
- },
- XYZ: {
- acceptReporters: false,
- items: [{
- text: "X",
- value: "x",
- },
- {
- text: "Y",
- value: "y",
- },
- {
- text: "Z",
- value: "z",
- },
- ],
- },
- materialProperties: {
- acceptReporters: false,
- items: [
- "|GENERAL| <-- not a property",
- {
- text: "Color",
- value: "color",
- },
- {
- text: "Map",
- value: "map",
- },
- {
- text: "Opacity",
- value: "opacity",
- },
- {
- text: "Transparent",
- value: "transparent",
- },
- {
- text: "Alpha Map",
- value: "alphaMap",
- },
- {
- text: "Alpha Test",
- value: "alphaTest",
- },
- {
- text: "Depth Test",
- value: "depthTest",
- },
- {
- text: "Depth Write",
- value: "depthWrite",
- },
- {
- text: "Color Write",
- value: "colorWrite",
- },
- {
- text: "Side",
- value: "side",
- },
- {
- text: "Visible",
- value: "visible",
- },
- /*
- { text: "Blending", value: "blending" },
- { text: "Blend Src", value: "blendSrc" },
- { text: "Blend Dst", value: "blendDst" },
- { text: "Blend Equation", value: "blendEquation" },
- { text: "Blend Src Alpha", value: "blendSrcAlpha" },
- { text: "Blend Dst Alpha", value: "blendDstAlpha" },
- { text: "Blend Equation Alpha", value: "blendEquationAlpha" },*/
- {
- text: "Blend Aplha",
- value: "blendAplha",
- },
- {
- text: "Blend Color",
- value: "blendColor",
- },
- {
- text: "Alpha Hash",
- value: "alphaHash",
- },
- {
- text: "Premultiplied Alpha",
- value: "premultipliedAlpha",
- },
-
- {
- text: "Tone Mapped",
- value: "toneMapped",
- },
- {
- text: "Fog",
- value: "fog",
- },
- {
- text: "Flat Shading",
- value: "flatShading",
- },
-
- "|MESH Standard / Physical| <-- not a property",
- {
- text: "Metalness",
- value: "metalness",
- },
- {
- text: "Metalness Map",
- value: "metalnessMap",
- },
- {
- text: "Roughness",
- value: "roughness",
- },
- {
- text: "Reflectivity",
- value: "reflectivity",
- },
- {
- text: "Roughness Map",
- value: "roughnessMap",
- },
- {
- text: "Emissive",
- value: "emissive",
- },
- {
- text: "Emissive Intensity",
- value: "emissiveIntensity",
- },
- {
- text: "Emissive Map",
- value: "emissiveMap",
- },
- {
- text: "Env Map",
- value: "envMap",
- },
- {
- text: "Env Map Intensity",
- value: "envMapIntensity",
- },
- {
- text: "Env Map Rotation",
- value: "envMapRotation",
- },
- {
- text: "Ior",
- value: "ior",
- },
- {
- text: "Refraction Ratio",
- value: "refractionRatio",
- },
- {
- text: "Clearcoat",
- value: "clearcoat",
- },
- {
- text: "Clearcoat Map",
- value: "clearcoatMap",
- },
- {
- text: "Clearcoat Roughness",
- value: "clearcoatRoughness",
- },
- {
- text: "Clearcoat Roughness Map",
- value: "clearcoatRoughnessMap",
- },
- {
- text: "Dispersion",
- value: "dispersion",
- },
- {
- text: "Sheen",
- value: "sheen",
- },
- {
- text: "Sheen Color",
- value: "sheenColor",
- },
- {
- text: "Sheen Color Map",
- value: "sheenColorMap",
- },
- {
- text: "Sheen Roughness",
- value: "sheenRoughness",
- },
- {
- text: "Sheen Roughness Map",
- value: "sheenRoughnessMap",
- },
- {
- text: "Specular Color",
- value: "specularColor",
- },
- {
- text: "Specular Color Map",
- value: "specularColorMap",
- },
- {
- text: "Specular Intensity",
- value: "specularIntensity",
- },
- {
- text: "Specular Intensity Map",
- value: "specularIntensityMap",
- },
- {
- text: "Transmission",
- value: "transmission",
- },
- {
- text: "Transmission Map",
- value: "transmissionMap",
- },
- {
- text: "Thickness",
- value: "thickness",
- },
- {
- text: "Thickness Map",
- value: "thicknessMap",
- },
- {
- text: "Anisotropy",
- value: "anisotropy",
- },
- {
- text: "Anisotropy Map",
- value: "anisotropyMap",
- },
- {
- text: "Anisotropy Rotation",
- value: "anisotropyRotation",
- },
- {
- text: "Attenuation Distance",
- value: "attenuationDistance",
- },
- {
- text: "Attenuation Color",
- value: "attenuationColor",
- },
- {
- text: "Thickness",
- value: "thickness",
- },
- {
- text: "Iridescence",
- value: "iridescence",
- },
- {
- text: "Iridescence Ior",
- value: "iridescenceIOR",
- },
- {
- text: "Iridescence Map",
- value: "iridescenceMap",
- },
- {
- text: "Iridescence Thickness Range",
- value: "iridescenceThicknessRange",
- },
-
- "|MESH Displacement / Normal / Bump| <-- not a property",
- {
- text: "Displacement Map",
- value: "displacementMap",
- },
- {
- text: "Displacement Scale",
- value: "displacementScale",
- },
- {
- text: "Displacement Bias",
- value: "displacementBias",
- },
- {
- text: "Bump Map",
- value: "bumpMap",
- },
- {
- text: "Bump Scale",
- value: "bumpScale",
- },
- {
- text: "Normal Map Type",
- value: "normalMapType",
- },
-
- "|MESH Matcap / Toon / Phong / Lambert / Basic| <-- not a property",
- {
- text: "Shininess",
- value: "shininess",
- },
-
- {
- text: "Wireframe",
- value: "wireframe",
- },
- {
- text: "Wireframe Linewidth",
- value: "wireframeLinewidth",
- },
- {
- text: "Wireframe Linecap",
- value: "wireframeLinecap",
- },
- {
- text: "Wireframe Linejoin",
- value: "wireframeLinejoin",
- },
-
- "|POINTS| <-- not a property",
- {
- text: "Size",
- value: "size",
- },
- {
- text: "Size Attenuation",
- value: "sizeAttenuation",
- },
-
- "|LINES| <-- not a property",
- {
- text: "Scale",
- value: "scale",
- },
- {
- text: "Dash Size",
- value: "dashSize",
- },
- {
- text: "Gap Size",
- value: "gapSize",
- },
-
- "|SPRITES| <-- not a property",
- {
- text: "Rotation",
- value: "rotation",
- },
- ],
- },
- blendModes: {
- acceptReporters: false,
- items: [{
- text: "No Blending",
- value: "NoBlending",
- },
- {
- text: "Normal Blending",
- value: "NormalBlending",
- },
- {
- text: "Additive Blending",
- value: "AdditiveBlending",
- },
- {
- text: "Subtractive Blending",
- value: "SubtractiveBlending",
- },
- {
- text: "Multiply Blending",
- value: "MultiplyBlending",
- },
- {
- text: "Custom Blending",
- value: "CustomBlending",
- },
- ],
- },
- depthModes: {
- acceptReporters: false,
- items: [{
- text: "Never Depth",
- value: "NeverDepth",
- },
- {
- text: "Always Depth",
- value: "AlwaysDepth",
- },
- {
- text: "Equal Depth",
- value: "EqualDepth",
- },
- {
- text: "Less Depth",
- value: "LessDepth",
- },
- {
- text: "Less Equal Depth",
- value: "LessEqualDepth",
- },
- {
- text: "Greater Equal Depth",
- value: "GreaterEqualDepth",
- },
- {
- text: "Greater Depth",
- value: "GreaterDepth",
- },
- {
- text: "Not Equal Depth",
- value: "NotEqualDepth",
- },
- ],
- },
- materialTypes: {
- acceptReporters: false,
- items: [{
- text: "Mesh Basic Material",
- value: "MeshBasicMaterial",
- },
- {
- text: "Mesh Standard Material",
- value: "MeshStandardMaterial",
- },
- {
- text: "Mesh Physical Material",
- value: "MeshPhysicalMaterial",
- },
- {
- text: "Mesh Lambert Material",
- value: "MeshLambertMaterial",
- },
- {
- text: "Mesh Phong Material",
- value: "MeshPhongMaterial",
- },
- {
- text: "Mesh Depth Material",
- value: "MeshDepthMaterial",
- },
- {
- text: "Mesh Normal Material",
- value: "MeshNormalMaterial",
- },
- {
- text: "Mesh Matcap Material",
- value: "MeshMatcapMaterial",
- },
- {
- text: "Mesh Toon Material",
- value: "MeshToonMaterial",
- },
- {
- text: "Line Basic Material",
- value: "LineBasicMaterial",
- },
- {
- text: "Line Dashed Material",
- value: "LineDashedMaterial",
- },
- {
- text: "Points Material",
- value: "PointsMaterial",
- },
- {
- text: "Sprite Material",
- value: "SpriteMaterial",
- },
- {
- text: "Shadow Material",
- value: "ShadowMaterial",
- },
- ],
- },
- textureModes: {
- acceptReporters: false,
- items: ["Pixelate", "Blur"],
- },
- textureStyles: {
- acceptReporters: false,
- items: ["Repeat", "Clamp"],
- },
- geometryTypes: {
- acceptReporters: false,
- items: [{
- text: "Box Geometry",
- value: "BoxGeometry",
- },
- {
- text: "Sphere Geometry",
- value: "SphereGeometry",
- },
- {
- text: "Cylinder Geometry",
- value: "CylinderGeometry",
- },
- {
- text: "Plane Geometry",
- value: "PlaneGeometry",
- },
- {
- text: "Circle Geometry",
- value: "CircleGeometry",
- },
- {
- text: "Torus Geometry",
- value: "TorusGeometry",
- },
- {
- text: "Torus Knot Geometry",
- value: "TorusKnotGeometry",
- },
- ],
- },
- modelsList: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".glb"));
- if (models.length < 1) return [
- ["Load a model! (GLB Loader category)"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- fonts: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".json"));
- if (models.length < 1) return [
- ["Load a font!"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- },
- };
- }
-
- addObject(args) {
- const object = new THREE[args.TYPE]();
-
- object.castShadow = true;
- object.receiveShadow = true;
-
- createObject(args.OBJECT3D, object, args.GROUP);
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D);
- const clone = object.clone(true);
- clone.name;
- createObject(args.NAME, clone, args.GROUP);
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D);
- let values = JSON.parse(args.VALUE);
-
- function degToRad(deg) {
- return (deg * Math.PI) / 180;
- }
-
- if (object.rigidBody) {
- const x = values[0];
- const y = values[1];
- const z = values[2];
- if (args.PROPERTY === "rotation") {
- const euler = new THREE.Euler(degToRad(x), degToRad(y), degToRad(z), "YXZ");
- const quaternion = new THREE.Quaternion();
- quaternion.setFromEuler(euler);
-
- object.rigidBody.setRotation({
- x: quaternion.x,
- y: quaternion.y,
- z: quaternion.z,
- w: quaternion.w,
- });
- } else if (args.PROPERTY === "position") {
- object.rigidBody.setTranslation({
- x: x,
- y: y,
- z: z,
- },
- true
- );
- }
- return;
- }
-
- if (object.isCamera == true && controls) {}
-
- if (args.PROPERTY === "rotation") {
- values = values.map((v) => (v * Math.PI) / 180);
- object.rotation.set(0, 0, 0);
- }
- if (object.isDirectionalLight == true) {
- object.pos = new THREE.Vector3(...values);
- console.log(true, values, object.pos);
- return;
- }
- object[args.PROPERTY].set(...values);
-
- if (object.type == "CubeCamera") object.updateCoordinateSystem();
- }
- /*
- changeObjectV3(args) {
- getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
-
- if (args.PROPERTY === "rotation") {
- values = values.map(v => v * Math.PI / 180);
- object.rotation.x += values[0]
- object.rotation.y += values[1]
- object.rotation.z += values[2]
- }
- else {
- object[args.PROPERTY].add(...values);
- }
- }
- changeObjectXV3(args) {
- getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "rotation") value = value * Math.PI / 180
-
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D);
- if (!object) return;
- let values = vector3ToString(object[args.PROPERTY]);
- if (args.PROPERTY === "rotation") {
- const toDeg = Math.PI / 180;
- values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
- }
-
- return JSON.stringify(values);
- }
- setObject(args) {
- let object = getObject(args.OBJECT3D);
- let value = args.VALUE;
- if (args.PROPERTY === "material") {
- const mat = materials[args.NAME];
- if (mat) value = mat;
- else value = undefined;
- } else if (args.PROPERTY === "geometry") {
- const geo = geometries[args.NAME];
- if (geo) value = geo;
- else value = undefined;
- } else value = !!value;
-
- if (value == undefined) return; //invalid geo/mat
- object[args.PROPERTY] = value;
- }
- getObject(args) {
- let object = getObject(args.OBJECT3D);
- if (!object) return;
- let value;
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value;
- }
- removeObject(args) {
- removeObject(args.OBJECT3D);
- }
- objectE(args) {
- return scene.children.map((o) => o.name).includes(args.NAME);
- }
-
- //defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
-
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
- const mat = materials[args.NAME];
-
- let value = args.VALUE;
-
- if (args.VALUE == "false") value = false;
-
- if (args.PROPERTY == "side") {
- value = args.VALUE == "D" ? THREE.DoubleSide : args.VALUE == "B" ? THREE.BackSide : THREE.FrontSide;
- } else if (args.PROPERTY === "normalScale") value = new THREE.Vector2(...JSON.parse(args.VALUE));
- else value = getAsset(value);
-
- console.log("o:", args.VALUE, typeof args.VALUE);
- console.log("r:", value, typeof value);
-
- mat[args.PROPERTY] = await value; //await incase its a texture
- mat.needsUpdate = true;
- }
- setBlending(args) {
- const mat = materials[args.NAME];
- mat.blending = THREE[args.VALUE];
- mat.premultipliedAlpha = true;
- mat.needsUpdate = true;
- }
- setDepth(args) {
- const mat = materials[args.NAME];
- mat.depthFunc = THREE[args.VALUE];
- mat.needsUpdate = true;
- }
- removeMaterial(args) {
- const mat = materials[args.NAME];
- mat.dispose();
- delete materials[args.NAME];
- }
- materialE(args) {
- return materials[args.NAME] ? true : false;
- }
-
- newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
- const geo = new THREE[args.TYPE]();
- geo.name = args.NAME;
-
- geometries[args.NAME] = geo;
- }
- setGeometry(args) {
- const geo = geometries[args.NAME];
- geo[args.PROPERTY] = args.VALUE;
-
- geo.needsUpdate = true;
- }
- removeGeometry(args) {
- const geo = geometries[args.NAME];
- geo.dispose();
- delete geometries[args.NAME];
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false;
- }
-
- newGeo(args) {
- const geometry = new THREE.BufferGeometry();
- geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME];
- const positions = args.POINTS.split(" ")
- .map((v) => JSON.parse(v))
- .flat(); //array of v3 of each vertex of each triangle
-
- geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
- geometry.computeVertexNormals();
-
- geometry.needsUpdate = true;
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME];
- const UVs = args.POINTS.split(" ")
- .map((v) => JSON.parse(v))
- .flat(); //array of v2 of each UV of each triangle
-
- geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
- geometry.needsUpdate = true;
- }
-
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
- geometry.name = args.NAME;
-
- geometries[args.NAME] = geometry;
- }
-
- async splineModel(args) {
- const model = await getModel(args.MODEL, args.NAME);
- if (!model) return console.warn("Model not found:", args.MODEL);
-
- const curve = getAsset(args.CURVE);
- const spacing = parseFloat(args.SPACING) || 1;
- const curveLength = curve.getLength();
- const divisions = Math.floor(curveLength / spacing);
-
- const geomList = [];
- const matList = [];
-
- for (let i = 0; i <= divisions; i++) {
- const t = i / divisions;
- const pos = curve.getPointAt(t);
- const tangent = curve.getTangentAt(t);
-
- const temp = model.clone(true);
- temp.position.copy(pos);
-
- const up = new THREE.Vector3(0, 0, 1);
- const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
- temp.quaternion.copy(quat);
-
- temp.updateMatrixWorld(true);
-
- temp.traverse((child) => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone();
- geom.applyMatrix4(child.matrixWorld);
- geomList.push(geom);
- matList.push(child.material); //.clone() ?
- }
- });
- }
-
- const validGeoms = geomList.filter((g) => {
- const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
- if (!ok) console.warn("geometry skipped:", g);
- return ok;
- });
-
- const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
- merged.computeBoundingBox();
- merged.computeBoundingSphere();
-
- merged.name = args.NAME;
- geometries[args.NAME] = merged;
- matList.name = args.NAME;
- materials[args.NAME] = matList;
- }
-
- async text(args) {
- const fontFile = runtime
- .getTargetForStage()
- .getSounds()
- .find((c) => c.name === args.FONT);
- if (!fontFile) return;
-
- const json = new TextDecoder().decode(fontFile.asset.data.buffer);
- const fontData = JSON.parse(json);
-
- const font = fontLoad.parse(fontData);
-
- const params = {
- font: font,
- size: JSON.parse(args.S),
- height: JSON.parse(args.D),
- curveSegments: JSON.parse(args.CS),
- bevelEnabled: false,
- };
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params);
- geometry.computeVertexNormals();
- geometry.center(); // optional, recenters the text
-
- geometry.name = args.NAME;
-
- geometries[args.NAME] = geometry;
- }
-
- async loadFont() {
- openFileExplorer(".json").then((files) => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- // From lily's assets
- // // Thank you PenguinMod for providing this code.
-
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- const buffer = arrayBuffer;
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Font loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading font.");
- }
-
- // End of PenguinMod
- };
-
- reader.readAsArrayBuffer(file);
- });
- }
- openConv() {
- {
- open("https://gero3.github.io/facetype.js/");
- }
- }
- }
- Scratch.extensions.register(new ThreeObjects());
-
- class ThreeLights {
- getInfo() {
- return {
- id: "threeLights",
- name: "Three Lights",
- color1: "#c7a22aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "addLight",
- blockType: Scratch.BlockType.COMMAND,
- text: "add light [NAME] type [TYPE] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myLight",
- },
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "lightTypes",
- },
- },
- },
- {
- opcode: "setLight",
- blockType: Scratch.BlockType.COMMAND,
- text: "set light [NAME][PROPERTY] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "lightProperties",
- defaultValue: "intensity",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myLight",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- exemptFromNormalization: true,
- },
- },
- },
- ],
- menus: {
- lightTypes: {
- acceptReporters: false,
- items: [{
- text: "Ambient Light",
- value: "AmbientLight",
- },
- {
- text: "Directional Light",
- value: "DirectionalLight",
- },
- {
- text: "Point Light",
- value: "PointLight",
- },
- {
- text: "Hemisphere Light",
- value: "HemisphereLight",
- },
- {
- text: "Spot Light",
- value: "SpotLight",
- },
- ],
- },
- lightProperties: {
- acceptReporters: false,
- items: [{
- text: "Color",
- value: "color",
- },
- {
- text: "Intensity",
- value: "intensity",
- },
- {
- text: "Cast Shadow?",
- value: "castShadow",
- },
- {
- text: "Ground Color (HemisphereLight)",
- value: "groundColor",
- },
- {
- text: "Map (SpotLight)",
- value: "map",
- },
- {
- text: "Distance (SpotLight)",
- value: "distance",
- },
- {
- text: "Decay (SpotLight)",
- value: "decay",
- },
- {
- text: "Penumbra (SpotLight)",
- value: "penumbra",
- },
- {
- text: "Angle/Size (SpotLight)",
- value: "angle",
- },
- {
- text: "Power (SpotLight)",
- value: "power",
- },
- {
- text: "Target Position (Directional/SpotLight)",
- value: "target",
- },
- ],
- },
- },
- };
- }
-
- addLight(args) {
- const light = new THREE[args.TYPE](0xffffff, 1);
-
- createObject(args.NAME, light, args.GROUP);
- lights[args.NAME] = light;
- if (light.type === "AmbientLight" || "HemisphereLight") return;
-
- light.castShadow = true;
- if (light.type === "PointLight") return;
- //Directional & Spot Light
- light.target.position.set(0, 0, 0);
- scene.add(light.target);
-
- light.pos = new THREE.Vector3(0, 0, 0);
-
- light.shadow.mapSize.width = 4096;
- light.shadow.mapSize.height = 2048;
-
- if (light.type === "SpotLight") {
- light.decay = 0;
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true;
- light.needsUpdate = true;
- }
-
- setLight(args) {
- const light = lights[args.NAME];
- if (!args.PROPERTY) return;
- if (args.PROPERTY === "target") {
- light.target.position.set(...JSON.parse(args.VALUE)); //vector3
- light.target.updateMatrixWorld();
- } else {
- light[args.PROPERTY] = getAsset(args.VALUE);
- }
- light.needsUpdate = true;
-
- if (light.type === "AmbientLight" || "HemisphereLight") return;
-
- light.shadow.camera.updateProjectionMatrix();
- light.shadow.needsUpdate = true;
- }
- }
- Scratch.extensions.register(new ThreeLights());
-
- class ThreeUtilities {
- getInfo() {
- return {
- id: "threeUtility",
- name: "Three Utilities",
- color1: "#3875c5ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "newVector2",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Vector [X] [Y]",
- arguments: {
- X: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- },
- },
- },
- {
- opcode: "newVector3",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Vector [X] [Y] [Z]",
- arguments: {
- X: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- },
- Z: {
- type: Scratch.ArgumentType.NUMBER,
- },
- },
- },
- "---",
- {
- opcode: "operateV3",
- blockType: Scratch.BlockType.REPORTER,
- text: "do [V3] [O] [V32]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- O: {
- type: Scratch.ArgumentType.STRING,
- menu: "operators",
- },
- V32: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- {
- opcode: "moveVector3",
- blockType: Scratch.BlockType.REPORTER,
- text: "move [S] steps in vector [V3] in direction [D3]",
- arguments: {
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- D3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- {
- opcode: "directionTo",
- blockType: Scratch.BlockType.REPORTER,
- text: "direction from [V3] to [T3]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,3]",
- },
- T3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- "---",
- {
- opcode: "newColor",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Color [HEX]",
- arguments: {
- HEX: {
- type: Scratch.ArgumentType.COLOR,
- defaultValue: "#9966ff",
- },
- },
- },
- {
- opcode: "newFog",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Fog [COLOR] [NEAR] [FAR]",
- arguments: {
- COLOR: {
- type: Scratch.ArgumentType.COLOR,
- defaultValue: "#9966ff",
- exemptFromNormalization: true,
- },
- NEAR: {
- type: Scratch.ArgumentType.NUMBER,
- },
- FAR: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 10,
- },
- },
- },
- {
- opcode: "newTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Texture [COSTUME] [MODE] [STYLE] repeat [X][Y]",
- arguments: {
- COSTUME: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- STYLE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureStyles",
- },
- X: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- {
- opcode: "newCubeTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Cube Texture X+[COSTUMEX0]X-[COSTUMEX1]Y+[COSTUMEY0]Y-[COSTUMEY1]Z+[COSTUMEZ0]Z-[COSTUMEZ1] [MODE] [STYLE] repeat [X][Y]",
- arguments: {
- COSTUMEX0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEX1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEY0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEY1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEZ0: {
- type: Scratch.ArgumentType.COSTUME,
- },
- COSTUMEZ1: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- STYLE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureStyles",
- },
- X: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- Y: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- {
- opcode: "newEquirectangularTexture",
- blockType: Scratch.BlockType.REPORTER,
- text: "New Equirectangular Texture [COSTUME] [MODE]",
- arguments: {
- COSTUME: {
- type: Scratch.ArgumentType.COSTUME,
- },
- MODE: {
- type: Scratch.ArgumentType.STRING,
- menu: "textureModes",
- },
- },
- },
- "---",
- {
- opcode: "curve",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.REPORTER,
- text: "generate curve [TYPE] from points [POINTS], closed: [CLOSED]",
- arguments: {
- TYPE: {
- type: Scratch.ArgumentType.STRING,
- menu: "curveTypes",
- },
- POINTS: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,3,0] [2.5,-1.5,0] [-2.5,-1.5,0]",
- },
- CLOSED: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "true",
- },
- },
- },
- "---",
- {
- opcode: "mouseDown",
- extensions: ["colours_sensing"],
- blockType: Scratch.BlockType.BOOLEAN,
- text: "mouse [BUTTON] [action]?",
- arguments: {
- BUTTON: {
- type: Scratch.ArgumentType.STRING,
- menu: "mouseButtons",
- },
- action: {
- type: Scratch.ArgumentType.STRING,
- menu: "mouseAction",
- },
- },
- },
- {
- opcode: "mousePos",
- extensions: ["colours_sensing"],
- blockType: Scratch.BlockType.REPORTER,
- text: "mouse position",
- arguments: {},
- },
- "---",
- {
- opcode: "getItem",
- extensions: ["colours_data_lists"],
- blockType: Scratch.BlockType.REPORTER,
- text: "get item [ITEM] of [ARRAY]",
- arguments: {
- ITEM: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- ARRAY: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: `["myObject", "myLight"]`,
- },
- },
- },
- {
- blockType: Scratch.BlockType.LABEL,
- text: "↳ Raycasting",
- },
- {
- opcode: "raycast",
- blockType: Scratch.BlockType.COMMAND,
- text: "Raycast from [V3] in direction [D3]",
- arguments: {
- V3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,3]",
- },
- D3: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,1]",
- },
- },
- },
- {
- opcode: "getRaycast",
- blockType: Scratch.BlockType.REPORTER,
- text: "get raycast [PROPERTY]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "raycastProperties",
- },
- },
- },
- ],
- menus: {
- materialProperties: {
- acceptReporters: false,
- items: [{
- text: "Color",
- value: "color",
- },
- {
- text: "Map (texture)",
- value: "map",
- },
- {
- text: "Alpha Map (texture)",
- value: "alphaMap",
- },
- {
- text: "Alpha Test (0-1)",
- value: "alphaTest",
- },
- {
- text: "Side (front/back/double)",
- value: "side",
- },
- {
- text: "Bump Map (texture)",
- value: "bumpMap",
- },
- {
- text: "Bump Scale",
- value: "bumpScale",
- },
- ],
- },
- textureModes: {
- acceptReporters: false,
- items: ["Pixelate", "Blur"],
- },
- textureStyles: {
- acceptReporters: false,
- items: ["Repeat", "Clamp"],
- },
- raycastProperties: {
- acceptReporters: false,
- items: [{
- text: "Intersected Object Names",
- value: "name",
- },
- {
- text: "Number of Objects",
- value: "number",
- },
- {
- text: "Intersected Objects distances",
- value: "distance",
- },
- ],
- },
- mouseButtons: {
- acceptReporters: false,
- items: ["left", "middle", "right"],
- },
- mouseAction: {
- acceptReporters: false,
- items: ["Down", "Clicked"],
- },
- curveTypes: {
- acceptReporters: false,
- items: ["CatmullRomCurve3"],
- },
- operators: {
- acceptReporters: false,
- items: ["+", "-", "*", "/", "=", "max", "min", "dot", "cross", "distance to", "angle to", "apply euler"],
- },
- },
- };
- }
- mouseDown(args) {
- if (args.action === "Down") return isMouseDown[args.BUTTON];
- if (args.action === "Clicked") {
- if (isMouseDown[args.BUTTON] == prevMouse[args.BUTTON]) return false;
- else prevMouse[args.BUTTON] = true;
- return true;
- }
- }
- mousePos(event) {
- return JSON.stringify(mouseNDC);
- }
- newVector3(args) {
- return JSON.stringify([args.X, args.Y, args.Z]);
- }
- operateV3(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3));
- const v32 = new THREE.Vector3(...JSON.parse(args.V32));
-
- let r;
- if (args.O == "+") r = v3.add(v32);
- else if (args.O == "-") r = v3.sub(v32);
- else if (args.O == "*") r = v3.multiply(v32);
- else if (args.O == "/") r = v3.divide(v32);
- else if (args.O == "=") r = v3.equals(v32);
- else if (args.O == "max") r = v3.max(v32);
- else if (args.O == "min") r = v3.min(v32);
- else if (args.O == "dot") r = v3.dot(v32);
- else if (args.O == "cross") r = v3.cross(v32);
- else if (args.O == "distance to") r = v3.distanceTo(v32);
- else if (args.O == "angle to") r = v3.angleTo(v32);
- else if (args.O == "apply euler") r = v3.applyEuler(new THREE.Euler(v32.x, v32.y, v32.z, eulerOrder));
-
- if (typeof r == "object") return JSON.stringify([r.x, r.y, r.z]);
- else return JSON.stringify(r);
- }
-
- newVector2(args) {
- return JSON.stringify([args.X, args.Y]);
- }
-
- moveVector3(args) {
- const currentPos = new THREE.Vector3(...JSON.parse(args.V3));
- const steps = Number(args.S);
-
- const [pitchInputDeg, yawInputDeg, rollInputDeg] = JSON.parse(args.D3).map(Number);
-
- const yaw = THREE.MathUtils.degToRad(yawInputDeg);
- const pitch = THREE.MathUtils.degToRad(pitchInputDeg);
- const roll = THREE.MathUtils.degToRad(rollInputDeg);
-
- const euler = new THREE.Euler(pitch, yaw, roll, eulerOrder);
-
- const forwardVector = new THREE.Vector3(0, 0, -1);
- const direction = forwardVector.applyEuler(euler).normalize();
-
- const newPos = currentPos.add(direction.multiplyScalar(steps));
- return JSON.stringify([newPos.x, newPos.y, newPos.z]);
- }
-
- directionTo(args) {
- const v3 = new THREE.Vector3(...JSON.parse(args.V3));
- const toV3 = new THREE.Vector3(...JSON.parse(args.T3));
-
- const direction = toV3.clone().sub(v3).normalize();
- // Pitch (X)
- const pitch = Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z));
- // Yaw (Y)
- const yaw = Math.atan2(direction.x, direction.z);
-
- // Roll always 0
- return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
- }
-
- newColor(args) {
- const color = new THREE.Color(args.HEX);
- const uuid = crypto.randomUUID();
- assets.colors[uuid] = color;
- return `colors/${uuid}`;
- }
- newFog(args) {
- const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
- const uuid = crypto.randomUUID();
- assets.fogs[uuid] = fog;
- return `fogs/${uuid}`;
- }
- async newTexture(args) {
- const textureURI = encodeCostume(args.COSTUME);
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
- async newCubeTexture(args) {
- const uris = [
- encodeCostume(args.COSTUMEX0),
- encodeCostume(args.COSTUMEX1),
- encodeCostume(args.COSTUMEY0),
- encodeCostume(args.COSTUMEY1),
- encodeCostume(args.COSTUMEZ0),
- encodeCostume(args.COSTUMEZ1),
- ];
- const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME);
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME;
- texture.mapping = THREE.EquirectangularReflectionMapping;
-
- setTexutre(texture, args.MODE);
- assets.textures[texture.uuid] = texture;
- return `textures/${texture.uuid}`;
- }
-
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g);
- if (!matches) return [];
-
- return matches.map((str) => {
- const nums = str
- .replace(/[\[\]\s]/g, "")
- .split(",")
- .map(Number);
- return new THREE.Vector3(nums[0] || 0, nums[1] || 0, nums[2] || 0);
- });
- }
- const points = parsePoints(args.POINTS);
- const curve = new THREE[args.TYPE](points);
- curve.closed = JSON.parse(args.CLOSED);
-
- const uuid = crypto.randomUUID();
- assets.curves[uuid] = curve;
- return `curves/${uuid}`;
- }
-
- getItem(args) {
- const items = JSON.parse(args.ARRAY);
- const item = items[args.ITEM - 1];
- if (!item) return "0";
- return item;
- }
-
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3));
- // rotation is in degrees => convert to radians first
- const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
-
- const euler = new THREE.Euler(rot[0], rot[1], rot[2], eulerOrder);
- const direction = new THREE.Vector3(0, 0, -1).applyEuler(euler).normalize();
-
- const raycaster = new THREE.Raycaster();
- //const camera = getObject(args.CAMERA)
- raycaster.set(origin, direction);
-
- const intersects = raycaster.intersectObjects(scene.children, true);
-
- raycastResult = intersects;
- }
- getRaycast(args) {
- if (args.PROPERTY === "number") return raycastResult.length;
- if (args.PROPERTY === "distance") return JSON.stringify(raycastResult.map((i) => i.distance));
- return JSON.stringify(raycastResult.map((i) => i.object[args.PROPERTY]));
- }
- }
- Scratch.extensions.register(new ThreeUtilities());
-
- class ThreeGLB {
- getInfo() {
- return {
- id: "threeGLB",
- name: "Three GLB Loader",
- color1: "#c53838ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- blockType: Scratch.BlockType.BUTTON,
- text: "Load GLB File",
- func: "loadModelFile",
- },
- {
- opcode: "addModel",
- blockType: Scratch.BlockType.COMMAND,
- text: "add [ITEM] as [NAME] to [GROUP]",
- arguments: {
- GROUP: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "scene",
- },
- ITEM: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelsList",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- },
- },
- {
- opcode: "getModel",
- blockType: Scratch.BlockType.REPORTER,
- text: "get object [PROPERTY] of [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "modelProperties",
- },
- },
- },
- {
- opcode: "playAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "play animation [ANAME] of [NAME], [TIMES] times",
- arguments: {
- TIMES: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "0",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "pauseAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [TOGGLE] animation [ANAME] of [NAME]",
- arguments: {
- TOGGLE: {
- type: Scratch.ArgumentType.NUMBER,
- menu: "pauseUn",
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- {
- opcode: "stopAnimation",
- blockType: Scratch.BlockType.COMMAND,
- text: "stop animation [ANAME] of [NAME]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myModel",
- },
- ANAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "walk",
- exemptFromNormalization: true,
- },
- },
- },
- ],
- menus: {
- modelProperties: {
- acceptReporters: false,
- items: [{
- text: "Animations",
- value: "animations",
- }, ],
- },
- pauseUn: {
- acceptReporters: true,
- items: [{
- text: "Pause",
- value: "true",
- },
- {
- text: "Unpasue",
- value: "false",
- },
- ],
- },
- modelsList: {
- acceptReporters: false,
- items: () => {
- const stage = runtime.getTargetForStage();
- if (!stage) return ["(loading...)"];
-
- // @ts-ignore
- const models = Scratch.vm.runtime
- .getTargetForStage()
- .getSounds()
- .filter((e) => e.name && e.name.endsWith(".glb"));
- if (models.length < 1) return [
- ["Load a model!"]
- ];
-
- // @ts-ignore
- return models.map((m) => [m.name]);
- },
- },
- },
- };
- }
-
- async loadModelFile() {
- openFileExplorer(".glb").then((files) => {
- const file = files[0];
- const reader = new FileReader();
-
- reader.onload = async (e) => {
- const arrayBuffer = e.target.result;
-
- {
- // From lily's assets
-
- // Thank you PenguinMod for providing this code.
- {
- const targetId = runtime.getTargetForStage().id; //util.target.id not working!
- const assetName = Cast.toString(file.name);
-
- //const res = await Scratch.fetch(args.URL);
- //const buffer = await res.arrayBuffer();
- const buffer = arrayBuffer;
-
- const storage = runtime.storage;
- const asset = storage.createAsset(
- storage.AssetType.Sound,
- storage.DataFormat.MP3,
- // @ts-ignore
- new Uint8Array(buffer),
- null,
- true
- );
-
- try {
- await vm.addSound(
- // @ts-ignore
- {
- asset,
- md5: asset.assetId + "." + asset.dataFormat,
- name: assetName,
- },
- targetId
- );
- alert("Model loaded successfully!");
- } catch (e) {
- console.error(e);
- alert("Error loading model.");
- }
- }
- // End of PenguinMod
- }
- };
-
- reader.readAsArrayBuffer(file);
- });
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME);
-
- createObject(args.NAME, group, args.GROUP);
- }
- getModel(args) {
- if (!models[args.NAME]) return;
- return Object.keys(models[args.NAME].actions).toString();
- }
-
- playAnimation(args) {
- const model = models[args.NAME];
- if (!model) {
- console.log("no model!");
- return;
- }
-
- const action = model.actions[args.ANAME]; //clones of models dont have a stored actions!
- if (!action) {
- console.log("no action!");
- return;
- }
-
- args.TIMES > 0 ? action.setLoop(THREE.LoopRepeat, args.TIMES) : action.setLoop(THREE.LoopRepeat, Infinity);
-
- action.reset().play();
- }
- stopAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.stop();
- }
- pauseAnimation(args) {
- const model = models[args.NAME];
- if (!model) return;
-
- const action = model.actions[args.ANAME];
- if (action) action.paused = args.TOGGLE;
- }
- }
- Scratch.extensions.register(new ThreeGLB());
-
- class ThreeAddons {
- getInfo() {
- return {
- id: "threeAddons",
- name: "Three Addons",
- color1: "#c538a2ff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- blockType: Scratch.BlockType.LABEL,
- text: "Orbit Control",
- },
- {
- opcode: "OrbitControl",
- blockType: Scratch.BlockType.COMMAND,
- text: "set addon Orbit Control [STATE]",
- arguments: {
- STATE: {
- type: Scratch.ArgumentType.STRING,
- menu: "onoff",
- },
- },
- },
-
- {
- blockType: Scratch.BlockType.LABEL,
- text: "Post Processing",
- },
- {
- opcode: "resetComposer",
- blockType: Scratch.BlockType.COMMAND,
- text: "reset composer",
- },
- {
- opcode: "bloom",
- blockType: Scratch.BlockType.COMMAND,
- text: "add bloom intensity:[I] smoothing:[S] threshold:[T] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- I: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.5,
- },
- T: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.5,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- },
- },
- {
- opcode: "godRays",
- blockType: Scratch.BlockType.COMMAND,
- text: "add god rays object:[NAME] density:[DENS] decay:[DEC] weight:[WEI] exposition:[EXP] | resolution:[RES] samples:[SAMP] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- DEC: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.95,
- },
- DENS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- EXP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.1,
- },
- WEI: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.4,
- },
- RES: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- SAMP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 64,
- },
- },
- },
- {
- opcode: "dots",
- blockType: Scratch.BlockType.COMMAND,
- text: "add dots scale:[S] angle:[A] | blend: [BLEND] opacity:[OP]",
- arguments: {
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- S: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- A: {
- type: Scratch.ArgumentType.ANGLE,
- defaultValue: 0,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "SCREEN",
- },
- },
- },
- {
- opcode: "depth",
- blockType: Scratch.BlockType.COMMAND,
- text: "add depth of field focusDistance:[FD] focalLength:[FL] bokehScale:[BS] | height:[H] | blend: [BLEND] opacity:[OP]",
- arguments: {
- FD: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 3,
- },
- FL: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 0.001,
- },
- BS: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 4,
- },
- H: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 240,
- },
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "NORMAL",
- },
- },
- },
- "---",
- {
- opcode: "custom",
- blockType: Scratch.BlockType.COMMAND,
- text: "add custom shader [NAME] with GLSL fragm [FRA] vert [VER] | blend: [BLEND] opacity:[OP]",
- arguments: {
- NAME: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myShader",
- },
- FRA: {
- type: Scratch.ArgumentType.STRING,
- },
- VER: {
- type: Scratch.ArgumentType.STRING,
- },
- BLEND: {
- type: Scratch.ArgumentType.STRING,
- menu: "blendModes",
- defaultValue: "NORMAL",
- },
- OP: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: 1,
- },
- },
- },
- ],
- menus: {
- onoff: {
- acceptReporters: true,
- items: [{
- text: "enabled",
- value: "1",
- },
- {
- text: "disabled",
- value: "0",
- },
- ],
- },
- blendModes: {
- acceptReporters: false,
- items: [
- "SKIP",
- "SET",
- "ADD",
- "ALPHA",
- "AVERAGE",
- "COLOR",
- "COLOR_BURN",
- "COLOR_DODGE",
- "DARKEN",
- "DIFFERENCE",
- "DIVIDE",
- "DST",
- "EXCLUSION",
- "HARD_LIGHT",
- "HARD_MIX",
- "HUE",
- "INVERT",
- "INVERT_RGB",
- "LIGHTEN",
- "LINEAR_BURN",
- "LINEAR_DODGE",
- "LINEAR_LIGHT",
- "LUMINOSITY",
- "MULTIPLY",
- "NEGATION",
- "NORMAL",
- "OVERLAY",
- "PIN_LIGHT",
- "REFLECT",
- "SCREEN",
- "SRC",
- "SATURATION",
- "SOFT_LIGHT",
- "SUBTRACT",
- "VIVID_LIGHT",
- ],
- },
- },
- };
- }
-
- OrbitControl(args) {
- if (controls) controls.dispose();
-
- console.log("creating...", OrbitControls);
- controls = new OrbitControls.OrbitControls(camera, threeRenderer.domElement);
- controls.enableDamping = true;
-
- controls.enabled = !!args.STATE;
- console.log(controls);
- }
-
- resetComposer() {
- composer.passes = [];
- passes = {};
- customEffects = [];
- updateComposers();
- }
-
- bloom(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const bloomEffect = new BloomEffect({
- intensity: args.I,
- luminanceThreshold: args.T, // ← correct key
- luminanceSmoothing: args.S,
- blendFunction: BlendFunction[args.BLEND],
- });
- bloomEffect.blendMode.opacity.value = args.OP;
-
- const pass = new EffectPass(camera, bloomEffect);
-
- composer.addPass(pass);
- }
-
- godRays(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- let object = getObject(args.NAME);
- const sun = object;
-
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- });
- godRays.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, godRays);
- composer.addPass(pass);
- }
-
- dots(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const dot = new DotScreenEffect({
- angle: args.A,
- scale: args.S,
- blendFunction: BlendFunction[args.BLEND],
- });
- dot.blendMode.opacity.value = args.OP;
- const pass = new EffectPass(camera, dot);
- composer.addPass(pass);
- }
-
- depth(args) {
- if (!camera || !scene) {
- if (alerts) alert("set a camera!");
- return;
- }
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- });
- dofEffect.blendMode.opacity.value = args.OP;
-
- const dofPass = new EffectPass(camera, dofEffect);
- composer.addPass(dofPass);
- }
-
- async custom(args) {
- function cleanGLSL(glslCode) {
- //delete multilines comments
- let cleanedCode = glslCode
- .replace(/\/\*[\s\S]*?\*\//g, " ")
- .replace(/ /g, "\n")
- .replace(/\/\/.*$/gm, " ")
- .replace(/; /g, ";\n");
-
- return cleanedCode;
- }
-
- let fs = cleanGLSL(`
- ${args.FRA}
- `);
- if (!args.FRA.trim()) {
- fs = `void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { outputColor = inputColor; }`;
- }
- const vs = cleanGLSL(`
- ${args.VER}
- `);
- console.log(fs);
- console.log(vs);
-
- const effect = new Effect("Custom", fs, {
- blendFunction: BlendFunction[args.BLEND],
- vertexShader: vs,
- uniforms: new Map([
- //uniforms usually in shaders... open to more!
- ["time", new THREE.Uniform(0.0)],
- [
- "resolution",
- new THREE.Uniform(new THREE.Vector2(threeRenderer.domElement.width, threeRenderer.domElement.height)),
- ],
- ]),
- defines: new Map([
- ["USE_TIME", "1"],
- ["USE_VERTEX_TRANSFORM", ""],
- ]),
- });
-
- effect.blendMode.opacity.value = args.OP;
-
- const pass = new EffectPass(camera, effect);
- composer.addPass(pass);
-
- customEffects.push(effect);
- }
- }
- Scratch.extensions.register(new ThreeAddons());
-
- class RapierPhysics {
- getInfo() {
- return {
- id: "rapierPhysics",
- name: "RAPIER Physics",
- color1: "#222222",
- color2: "#203024ff",
- color3: "#78f07eff",
- blocks: [{
- opcode: "createWorld",
- blockType: Scratch.BlockType.COMMAND,
- text: "create world | gravity:[G]",
- arguments: {
- G: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,-9.81,0]",
- },
- },
- },
- {
- opcode: "getWorld",
- blockType: Scratch.BlockType.REPORTER,
- text: "get world [PROPERTY]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "wProp",
- },
- },
- },
- "---",
- {
- opcode: "objectPhysics",
- blockType: Scratch.BlockType.COMMAND,
- text: "enable physics for object [OBJECT] [state] | rigidBody [type] | collider [collider] mass [mass] density [density] friction [friction] sensor [state2]",
- arguments: {
- state2: {
- type: Scratch.ArgumentType.STRING,
- menu: "state2",
- },
- state: {
- type: Scratch.ArgumentType.STRING,
- menu: "state",
- defaultValue: "true",
- },
- type: {
- type: Scratch.ArgumentType.STRING,
- menu: "objectTypes",
- defaultValue: "dynamic",
- },
- collider: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderTypes",
- defaultValue: "cuboid",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- mass: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- density: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "1",
- },
- friction: {
- type: Scratch.ArgumentType.NUMBER,
- defaultValue: "0.5",
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.LABEL,
- text: "- RigidBody",
- },
- {
- opcode: "setRB",
- blockType: Scratch.BlockType.COMMAND,
- text: "set rigidbody [PROPERTY] of [OBJECT] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "rigidBodySets",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "getRB",
- blockType: Scratch.BlockType.REPORTER,
- text: "get rigidbody [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "rigidBodyProperties",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "lockObjectAxis",
- blockType: Scratch.BlockType.COMMAND,
- text: "lock rigidbody [OBJECT] [PROPERTY] on x:[X] y:[Y] z:[Z]",
- arguments: {
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "lockAxes",
- },
- X: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- Y: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- Z: {
- type: Scratch.ArgumentType.STRING,
- menu: "tf",
- },
- },
- },
- "---",
- {
- opcode: "addForce",
- blockType: Scratch.BlockType.COMMAND,
- text: "set [PROPERTY] of [OBJECT] to [VALUE] in [SPACE] space",
- arguments: {
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,10,0]",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "forces",
- defaultValue: "addForce",
- },
- SPACE: {
- type: Scratch.ArgumentType.STRING,
- menu: "spaces",
- defaultValue: "world",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "resetForces",
- blockType: Scratch.BlockType.COMMAND,
- text: "reset [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "resetF",
- defaultValue: "resetForces",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "enableCCD",
- blockType: Scratch.BlockType.COMMAND,
- text: "enable Continuous Collision Detection for [OBJECT] [state]",
- arguments: {
- state: {
- type: Scratch.ArgumentType.STRING,
- menu: "state",
- defaultValue: "true",
- },
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "oPropS",
- defaultValue: "physics",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "fixedJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create FIXED joint between [ObjA] & [ObjB] | anchor A: [VA] [RA] B: [VB] [RB]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- RA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- RB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- },
- },
- {
- opcode: "sphericalJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create SPHERICAL joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- },
- },
- {
- opcode: "revoluteJoint",
- blockType: Scratch.BlockType.COMMAND,
- text: "create REVOLUTE joint between [ObjA] & [ObjB] | anchor A: [VA] B: [VB] | axis: [X]",
- arguments: {
- ObjA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- ObjB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObjectB",
- },
- VA: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,0,0]",
- },
- VB: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[0,1,0]",
- },
- X: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "[1,0,0]",
- },
- },
- },
- "---",
- {
- blockType: Scratch.BlockType.LABEL,
- text: "- Collider",
- },
- {
- opcode: "setC",
- blockType: Scratch.BlockType.COMMAND,
- text: "set collider [PROPERTY] of [OBJECT] to [VALUE]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderSets",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- VALUE: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "1",
- },
- },
- },
- {
- opcode: "getC",
- blockType: Scratch.BlockType.REPORTER,
- text: "get collider [PROPERTY] of [OBJECT]",
- arguments: {
- PROPERTY: {
- type: Scratch.ArgumentType.STRING,
- menu: "colliderProperties",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- "---",
- {
- opcode: "sensorSingle",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "is sensor [SENSOR] touching [OBJECT]?",
- arguments: {
- SENSOR: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySensor",
- },
- OBJECT: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "myObject",
- },
- },
- },
- {
- opcode: "sensorAll",
- blockType: Scratch.BlockType.REPORTER,
- text: "objects touching sensor [SENSOR]",
- arguments: {
- SENSOR: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "mySensor",
- },
- },
- },
- ],
- menus: {
- wProp: {
- acceptReporters: false,
- items: [{
- text: "Gravity",
- value: "gravity",
- },
- {
- text: "log to console",
- value: "log",
- },
- ],
- },
- tf: {
- acceptReporters: true,
- items: [{
- text: "false",
- value: "false",
- },
- {
- text: "true",
- value: "true",
- },
- ],
- },
- lockAxes: {
- acceptReporters: false,
- items: [{
- text: "Translation",
- value: "setEnabledTranslations",
- },
- {
- text: "Rotation",
- value: "setEnabledRotations",
- },
- ],
- },
- rigidBodyProperties: {
- acceptReporters: false,
- items: [{
- text: "Type",
- value: "bodyType",
- },
- {
- text: "Linear Velocity",
- value: "linvel",
- },
- {
- text: "Angular Velocity",
- value: "angvel",
- },
- {
- text: "Translation (position)",
- value: "translation",
- },
- {
- text: "Rotation (quaternion)",
- value: "rotation",
- },
- {
- text: "Mass",
- value: "mass",
- },
- //{text: "Center of Mass", value: "centerOfMass"},
- {
- text: "Linear Damping",
- value: "linearDamping",
- },
- {
- text: "Angular Damping",
- value: "angularDamping",
- },
- {
- text: "Is Sleeping?",
- value: "isSleeping",
- },
- //{text: "Can Sleep?", value: "isCanSleep"},
- {
- text: "Gravity Scale",
- value: "gravityScale",
- },
- {
- text: "Is Fixed?",
- value: "isFixed",
- },
- {
- text: "Is Dynamic?",
- value: "isDynamic",
- },
- {
- text: "Is Kinematic?",
- value: "isKinematic",
- },
- //{text: "Sleeping", value: "sleeping"}
- ],
- },
- rigidBodySets: {
- acceptReporters: false,
- items: [
- //{text: "Linear Velocity", value: "setLinvel"},
- //{text: "Angular Velocity", value: "setAngvel"},
- //{text: "Mass", value: "setMass"},
- {
- text: "Gravity Scale",
- value: "setGravityScale",
- },
- //{text: "Can Sleep?", value: "setCanSleep"},
- //{text: "Sleeping", value: "sleeping"},
- {
- text: "Linear Damping",
- value: "setLinearDamping",
- },
- {
- text: "Angular Damping",
- value: "setAngularDamping",
- },
- {
- text: "Is Fixed?",
- value: "isFixed",
- },
- {
- text: "Is Dynamic?",
- value: "isDynamic",
- },
- {
- text: "Is Kinematic?",
- value: "isKinematic",
- },
- ],
- },
- colliderProperties: {
- acceptReporters: false,
- items: [
- //{text: "Collider Type", value: "type"},
- {
- text: "Is Sensor?",
- value: "isSensor",
- },
- {
- text: "Friction",
- value: "friction",
- },
- {
- text: "Restitution",
- value: "restitution",
- },
- {
- text: "Density",
- value: "density",
- },
- {
- text: "Mass",
- value: "mass",
- },
- {
- text: "Position",
- value: "translation",
- },
- {
- text: "Rotation",
- value: "rotation",
- },
- //{text: "Area", value: "area"},
- {
- text: "Volume",
- value: "volume",
- },
- {
- text: "Collision Groups",
- value: "collisionGroups",
- },
- //{text: "Collision Mask", value: "collisionMask"},
- //{text: "Is Enabled?", value: "enabled"},
- //{text: "Contact Count", value: "contactCount"},
- //{text: "RigidBody Handle", value: "rigidBody"}
- ],
- },
- colliderSets: {
- acceptReporters: false,
- items: [{
- text: "Friction",
- value: "setFriction",
- },
- {
- text: "Restitution",
- value: "setRestitution",
- },
- {
- text: "Density",
- value: "setDensity",
- },
- {
- text: "Is Sensor?",
- value: "setSensor",
- },
- {
- text: "Collision Groups",
- value: "setCollisionGroups",
- },
- //{text: "Enabled", value: "enabled"}, // object.collider.enabled = bool
- //{text: "Position Offset", value: "setTranslation"},
- //{text: "Rotation Offset", value: "setRotation"}
- ],
- },
- state: {
- acceptReporters: true,
- items: [{
- text: "on",
- value: "true",
- },
- {
- text: "off",
- value: "false",
- },
- ],
- },
- state2: {
- acceptReporters: true,
- items: [{
- text: "false",
- value: "false",
- },
- {
- text: "true (must be fixed)",
- value: "true",
- },
- ],
- },
- spaces: {
- acceptReporters: false,
- items: [{
- text: "World",
- value: "world",
- },
- {
- text: "Local",
- value: "local",
- },
- ],
- },
- objectTypes: {
- acceptReporters: false,
- items: [{
- text: "Dynamic",
- value: "dynamic",
- },
- {
- text: "Fixed",
- value: "fixed",
- },
- {
- text: "Kinematic Position Based",
- value: "kinematicPositionBased",
- },
- ],
- },
- colliderTypes: {
- acceptReporters: false,
- items: [{
- text: "Box, Rectangle, cuboid",
- value: "cuboid",
- },
- {
- text: "Sphere, ball",
- value: "ball",
- },
- {
- text: "Custom, complex simple shapes, convexHull",
- value: "convexHull",
- },
- {
- text: "Precision, TriMesh",
- value: "trimesh",
- },
- ],
- },
- forces: {
- acceptReporters: false,
- items: [{
- text: "Force",
- value: "addForce",
- },
- {
- text: "Torque (rotation)",
- value: "addTorque",
- },
- {
- text: "Apply Impulse",
- value: "applyImpulse",
- },
- {
- text: "Apply Torque Impulse (rotation)",
- value: "applyTorqueImpulse",
- },
- {
- text: "Linear Velocity",
- value: "setLinvel",
- },
- {
- text: "Angular Velocity",
- value: "setAngvel",
- },
- ],
- },
- resetF: {
- acceptReporters: false,
- items: [{
- text: "Forces",
- value: "resetForces",
- },
- {
- text: "Torques",
- value: "resetTorques",
- },
- ],
- },
- },
- };
- }
- joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
- }
-
- fixedJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- let RA = JSON.parse(args.RA).map(Number);
- let RB = JSON.parse(args.RB).map(Number);
-
- RA = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RA[0]),
- THREE.MathUtils.degToRad(RA[1]),
- THREE.MathUtils.degToRad(RA[2])
- )
- );
- RB = new THREE.Quaternion().setFromEuler(
- new THREE.Euler(
- THREE.MathUtils.degToRad(RB[0]),
- THREE.MathUtils.degToRad(RB[1]),
- THREE.MathUtils.degToRad(RB[2])
- )
- );
-
- const data = RAPIER.JointData.fixed({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- },
- RA, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- },
- RB
- );
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- sphericalJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
-
- const data = RAPIER.JointData.spherical({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- revoluteJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- const x = JSON.parse(args.X).map(Number);
-
- const data = RAPIER.JointData.revolute({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- }, {
- x: x[0],
- y: x[1],
- z: x[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- prismaticJoint(args) {
- const VA = JSON.parse(args.VA).map(Number);
- const VB = JSON.parse(args.VB).map(Number);
- const x = JSON.parse(args.X).map(Number);
-
- const data = RAPIER.JointData.prismatic({
- x: VA[0],
- y: VA[1],
- z: VA[2],
- }, {
- x: VB[0],
- y: VB[1],
- z: VB[2],
- }, {
- x: x[0],
- y: x[1],
- z: x[2],
- });
- const objectA = getObject(args.ObjA);
- let object = getObject(args.ObjB);
- this.joint(data, objectA, object);
- }
-
- createWorld(args) {
- const v3 = JSON.parse(args.G).map(Number);
- const gravity = {
- x: v3[0],
- y: v3[1],
- z: v3[2],
- };
- physicsWorld = new RAPIER.World(gravity);
-
- console.log(physicsWorld);
- }
-
- getWorld(args) {
- if (args.PROPERTY === "log") {
- console.log(physicsWorld);
- return "logged";
- }
- return JSON.stringify(physicsWorld[args.PROPERTY]);
- }
-
- setRB(args) {
- let value = args.VALUE;
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
- let object = getObject(args.OBJECT);
- object.rigidBody[args.PROPERTY](value);
- }
- setC(args) {
- let value = args.VALUE;
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
- let object = getObject(args.OBJECT);
- object.collider[args.PROPERTY](value);
- }
-
- getRB(args) {
- let object = getObject(args.OBJECT);
- return JSON.stringify(object.rigidBody[args.PROPERTY]());
- }
- getC(args) {
- let object = getObject(args.OBJECT);
- return JSON.stringify(object.collider[args.PROPERTY]());
- }
-
- lockObjectAxis(args) {
- let object = getObject(args.OBJECT);
- const x = !JSON.parse(args.X);
- const y = !JSON.parse(args.Y);
- const z = !JSON.parse(args.Z);
- object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
- }
-
- objectPhysics(args) {
- let object = getObject(args.OBJECT);
- object.physics = JSON.parse(args.state);
-
- if (JSON.parse(args.state)) {
- //if already exists delete:
- if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody);
- object.rigidBody = null;
- object.collider = null;
- }
- /*asing a rigidbody and collider to object and add them to physicsWorld*/
- let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
- .setTranslation(object.position.x, object.position.y, object.position.z)
- .setRotation({
- w: object.quaternion._w,
- x: object.quaternion._x,
- y: object.quaternion._y,
- z: object.quaternion._z,
- });
-
- let colliderDesc;
- switch (args.collider) {
- case "cuboid":
- colliderDesc = createCuboidCollider(object);
- break;
- case "ball":
- colliderDesc = createBallCollider(object);
- break;
- case "convexHull":
- colliderDesc = createConvexHullCollider(object);
- break;
- case "trimesh":
- colliderDesc = TriMesh(object);
- break;
- }
- colliderDesc
- .setSensor(JSON.parse(args.state2))
- .setMass(args.mass)
- .setDensity(args.density)
- .setFriction(args.friction);
-
- let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
- let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
-
- object.rigidBody = rigidBody;
- object.collider = collider;
- } else {
- /*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody);
- object.rigidBody = null;
- object.collider = null;
- }
- }
-
- enableCCD(args) {
- let object = getObject(args.OBJECT);
- if (object.physics) {
- let rigidBody = object.rigidBody;
- rigidBody.enableCcd(JSON.parse(args.state));
- }
- }
-
- addForce(args) {
- let object = getObject(args.OBJECT);
- const vector = JSON.parse(args.VALUE).map(Number);
-
- let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
- if (args.SPACE === "local") {
- force.applyQuaternion(object.quaternion);
- }
-
- object.rigidBody[args.PROPERTY](force, true);
- }
-
- resetForces(args) {
- rigidBody[args.PROPERTY](true);
- }
-
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR);
-
- let object = getObject(args.OBJECT);
-
- let touching = false;
- physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
- if (otherCollider === object.collider) touching = true;
- });
-
- return touching;
- }
-
- sensorAll(args) {
- const sensor = getObject(args.SENSOR);
-
- const touchedObjects = [];
-
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
- // find owner of collider
- const otherObject = scene.children.find((o) => o.collider === otherCollider);
- console.log(otherCollider);
- if (otherObject) touchedObjects.push(otherObject.name);
- });
-
- return JSON.stringify(touchedObjects);
- }
- }
- Scratch.extensions.register(new RapierPhysics());
-
- //Thanks to the PointerLock extension of Turbowarp
- const mouse = vm.runtime.ioDevices.mouse;
- let isLocked = false;
- let isPointerLockEnabled = false;
-
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
-
- const postMouseData = (e, isDown) => {
- const {
- movementX,
- movementY
- } = e;
- const {
- width,
- height
- } = rect;
- const x = mouse._clientX + movementX;
- const y = mouse._clientY - movementY;
- mouse._clientX = x;
- mouse._scratchX = mouse.runtime.stageWidth * (x / width - 0.5);
- mouse._clientY = y;
- mouse._scratchY = mouse.runtime.stageHeight * (y / height - 0.5);
- if (typeof isDown === "boolean") {
- const data = {
- button: e.button,
- isDown,
- };
- originalPostIOData(data);
- }
- };
-
- const mouseDevice = vm.runtime.ioDevices.mouse;
- const originalPostIOData = mouseDevice.postData.bind(mouseDevice);
- mouseDevice.postData = (data) => {
- if (!isPointerLockEnabled) {
- return originalPostIOData(data);
- }
- };
-
- document.addEventListener(
- "mousedown",
- (e) => {
- // @ts-expect-error
- if (threeRenderer.domElement.contains(e.target)) {
- if (isLocked) {
- postMouseData(e, true);
- } else if (isPointerLockEnabled) {
- threeRenderer.domElement.requestPointerLock();
- }
- }
- },
- true
- );
- document.addEventListener(
- "mouseup",
- (e) => {
- if (isLocked) {
- postMouseData(e, false);
- // @ts-expect-error
- } else if (isPointerLockEnabled && threeRenderer.domElement.contains(e.target)) {
- threeRenderer.domElement.requestPointerLock();
- }
- },
- true
- );
- document.addEventListener(
- "mousemove",
- (e) => {
- if (isLocked) {
- postMouseData(e);
- }
- },
- true
- );
-
- document.addEventListener("pointerlockchange", () => {
- isLocked = document.pointerLockElement === threeRenderer.domElement;
- });
- document.addEventListener("pointerlockerror", (e) => {
- console.error("Pointer lock error", e);
- });
-
- const oldStep = vm.runtime._step;
- vm.runtime._step = function(...args) {
- const ret = oldStep.call(this, ...args);
- if (isPointerLockEnabled) {
- const {
- width,
- height
- } = rect;
- mouse._clientX = width / 2;
- mouse._clientY = height / 2;
- mouse._scratchX = 0;
- mouse._scratchY = 0;
- }
- return ret;
- };
-
- vm.runtime.on("PROJECT_LOADED", () => {
- isPointerLockEnabled = false;
- if (isLocked) {
- document.exitPointerLock();
- }
- });
-
- class Pointerlock {
- getInfo() {
- return {
- id: "threepointerlockmod",
- name: "Pointerlock for Extra 3D",
- color1: "#8a8a8aff",
- color2: "#222222",
- color3: "#222222",
-
- blocks: [{
- opcode: "setLocked",
- blockType: Scratch.BlockType.COMMAND,
- text: "set pointer lock [enabled]",
- arguments: {
- enabled: {
- type: Scratch.ArgumentType.STRING,
- defaultValue: "true",
- menu: "enabled",
- },
- },
- },
- {
- opcode: "isLocked",
- blockType: Scratch.BlockType.BOOLEAN,
- text: "pointer locked?",
- },
- ],
- menus: {
- enabled: {
- acceptReporters: true,
- items: [{
- text: "enabled",
- value: "true",
- },
- {
- text: "disabled",
- value: "false",
- },
- ],
- },
- },
- };
- }
-
- setLocked(args) {
- isPointerLockEnabled = Scratch.Cast.toBoolean(args.enabled) === true;
- if (!isPointerLockEnabled && isLocked) {
- document.exitPointerLock();
- }
- }
-
- isLocked() {
- return isLocked;
- }
- }
- Scratch.extensions.register(new Pointerlock());
- });
-})(Scratch);
From 7bd8d1257bdbc5d6fe1a3f2ec2acd1cd0930fad3 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 17:09:38 -0600
Subject: [PATCH 15/32] fix
---
threejsD.js | 908 +++++++++++++++++++++++++++++++---------------------
1 file changed, 548 insertions(+), 360 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 9ea56ec..bc6db57 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -59,22 +59,23 @@
let RAPIER;
let physicsWorld;
- let threeRenderer
- let scene
- let camera
- let eulerOrder = "YXZ"
-
- let composer
- let passes = {}
- let customEffects = []
- let renderTargets = {}
-
- let materials = {}
- let geometries = {}
- let lights = {}
- let models = {}
-
- let assets = { //should i place materials, geometries; inside too?
+ let threeRenderer;
+ let scene;
+ let camera;
+ let eulerOrder = "YXZ";
+
+ let composer;
+ let passes = {};
+ let customEffects = [];
+ let renderTargets = {};
+
+ let materials = {};
+ let geometries = {};
+ let lights = {};
+ let models = {};
+
+ let assets = {
+ //should i place materials, geometries; inside too?
textures: {},
colors: {},
fogs: {},
@@ -85,17 +86,17 @@
let raycastResult = [];
function resetor(level) {
- camera = undefined
- composer.reset()
+ camera = undefined;
+ composer.reset();
- passes = {}
- customEffects = []
- renderTargets = {}
+ passes = {};
+ customEffects = [];
+ renderTargets = {};
- materials = {}
- geometries = {}
- lights = {}
- models = {}
+ materials = {};
+ geometries = {};
+ lights = {};
+ models = {};
if (level > 0) {
assets = {
@@ -128,42 +129,62 @@
return [x, y, z];
}
- object.add(content)
+ //objects
+ function createObject(name, content, parentName) {
+ let object = getObject(name, true);
+ if (object) {
+ removeObject(name);
+ alerts ? alert(name + " already exsisted, will replace!") : null;
}
- function removeObject(name) {
- let object = getObject(name)
- if (!object) return
+ content.name = name;
+ content.rotation._order = eulerOrder;
+ parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+ content.physics = false;
- scene.remove(object)
+ object.add(content);
+ }
- if (object.rigidBody) {
- physicsWorld.removeCollider(object.collider, true)
- physicsWorld.removeRigidBody(object.rigidBody, true)
- object.rigidBody = null
- object.collider = null
- }
- if (object.isLight) {
- delete(lights[name])
- }
+ function removeObject(name) {
+ let object = getObject(name);
+ if (!object) return;
+
+ scene.remove(object);
+
+ if (object.rigidBody) {
+ physicsWorld.removeCollider(object.collider, true);
+ physicsWorld.removeRigidBody(object.rigidBody, true);
+ object.rigidBody = null;
+ object.collider = null;
}
- function getObject(name, isNew) {
- let object = null
- if (!scene) {
- alerts ? alert("Can not get " + name + ". Create a scene first!") : null; return;}
- object = scene.getObjectByName(name)
- if (!object && !isNew) {alerts ? alert(name + " does not exist! Add it to scene"):null; return;}
- return object
+ if (object.isLight) {
+ delete lights[name];
}
+ }
-//materials
- function encodeCostume (name) {
- if (name.startsWith("data:image/")) return name
- return Scratch.vm.editingTarget.sprite.costumes.find(c => c.name === name).asset.encodeDataURI()
+ function getObject(name, isNew) {
+ let object = null;
+ if (!scene) {
+ alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
+ return;
+ }
+ object = scene.getObjectByName(name);
+ if (!object && !isNew) {
+ alerts ? alert(name + " does not exist! Add it to scene") : null;
+ return;
}
- function setTexutre (texture, mode, style, x, y) {
- texture.colorSpace = THREE.SRGBColorSpace
+ return object;
+ }
+
+ //materials
+ function encodeCostume(name) {
+ if (name.startsWith("data:image/")) return name;
+ return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ }
- if (mode === "Pixelate") {
+ function setTexutre(texture, mode, style, x, y) {
+ texture.colorSpace = THREE.SRGBColorSpace;
+
+ if (mode === "Pixelate") {
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
} else {
@@ -226,12 +247,12 @@
// Ensure matrices are updated.
light.target.updateMatrixWorld();
- light.shadow.camera.updateProjectionMatrix()
- light.shadow.needsUpdate = true;
-}
-//composer
-function updateComposers() {
- if (!camera || !scene) return; // nothing to do yet
+ light.shadow.camera.updateProjectionMatrix();
+ light.shadow.needsUpdate = true;
+ }
+ //composer
+ function updateComposers() {
+ if (!camera || !scene) return; // nothing to do yet
// always recreate the RenderPass to point to the current scene/camera
passes["Render"] = new RenderPass(scene, camera);
@@ -422,8 +443,11 @@ function updateComposers() {
return JSON.parse(path); //boolean or number
}
- return JSON.parse(path) //boolean or number
-}
+ let mouseNDC = [0, 0];
+ //loops/init
+ function stopLoop() {
+ if (!running) return;
+ running = false;
if (loopId) {
cancelAnimationFrame(loopId);
@@ -476,11 +500,11 @@ function updateComposers() {
powerPreference: "high-performance",
antialias: false,
stencil: false,
- depth: true
- })
- threeRenderer.setPixelRatio(window.devicePixelRatio)
- threeRenderer.outputColorSpace = THREE.SRGBColorSpace // correct colors
- threeRenderer.toneMapping = THREE.ACESFilmicToneMapping // HDR look (test)
+ depth: true,
+ });
+ threeRenderer.setPixelRatio(window.devicePixelRatio);
+ threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
+ threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
//threeRenderer.toneMappingExposure = 1.0 //(test)
threeRenderer.shadowMap.enabled = true;
@@ -532,23 +556,26 @@ function updateComposers() {
}
}
- const loop = () => {
- if (!running) return
- //RAPIER
- if (physicsWorld && scene) {
- physicsWorld.step()
-
- scene.children.forEach(obj => {
- if (!(obj.isMesh) || !(obj.physics)) return
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation())
- obj.quaternion.copy(obj.rigidBody.rotation())
- }
- })
-
- }
- if (scene && camera) {
- if (controls) controls.update()
+ function startRenderLoop() {
+ if (running) return;
+ running = true;
+
+ const loop = () => {
+ if (!running) return;
+ //RAPIER
+ if (physicsWorld && scene) {
+ physicsWorld.step();
+
+ scene.children.forEach((obj) => {
+ if (!obj.isMesh || !obj.physics) return;
+ if (obj.rigidBody) {
+ obj.position.copy(obj.rigidBody.translation());
+ obj.quaternion.copy(obj.rigidBody.rotation());
+ }
+ });
+ }
+ if (scene && camera) {
+ if (controls) controls.update();
const delta = clock.getDelta();
Object.values(models).forEach((model) => {
@@ -605,9 +632,13 @@ function updateComposers() {
const w = canvas.width;
const h = canvas.height;
-function resize() {
- const w = canvas.width
- const h = canvas.height
+ threeRenderer.setSize(w, h);
+ composer.setSize(w, h);
+ customEffects.forEach((e) => {
+ if (e.uniforms.get("resolution")) {
+ e.uniforms.get("resolution").value.set(w, h);
+ }
+ });
if (camera) {
camera.aspect = w / h;
@@ -697,8 +728,11 @@ function resize() {
}
Scratch.extensions.register(new ThreeRenderer());
- }
- Scratch.extensions.register(new ThreeRenderer())
+ class ThreeScene {
+ constructor() {
+ this.THREE = THREE;
+ this.scenes = {};
+ }
getInfo() {
return {
@@ -800,37 +834,41 @@ function resize() {
};
}
- newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME
- scene.background = new THREE.Color("#222")
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
-
- resetor(0)
- }
+ newScene(args) {
+ scene = new THREE.Scene();
+ scene.name = args.NAME;
+ scene.background = new THREE.Color("#222");
+ //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
+ this.scenes = {
+ ...this.scenes,
+ ...scene,
+ };
+ resetor(0);
+ }
- reset() {
- resetor(1)
- }
+ reset() {
+ resetor(1);
+ }
- async setSceneProperty(args) {
+ async setSceneProperty(args) {
const property = args.PROPERTY;
const value = getAsset(args.VALUE);
scene[property] = value;
- }
- getSceneObjects(args){
- const names = [];
- if (args.THING === "Objects") {
- scene.traverse(obj => {
- if (obj.name) names.push(obj.name); //if it has a name, add to list!
- });
- }
- else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials))
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries))
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights))
- else if (args.THING === "Scene Properties") {console.log(scene); return "check console"}
- else if (args.THING === "Other assets") return JSON.stringify(assets)
+ }
+ getSceneObjects(args) {
+ const names = [];
+ if (args.THING === "Objects") {
+ scene.traverse((obj) => {
+ if (obj.name) names.push(obj.name); //if it has a name, add to list!
+ });
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
+ else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ else if (args.THING === "Scene Properties") {
+ console.log(scene);
+ return "check console";
+ } else if (args.THING === "Other assets") return JSON.stringify(assets);
return JSON.stringify(names); // if objects
}
@@ -1031,12 +1069,28 @@ function resize() {
const object = new THREE.PerspectiveCamera(90, v2.x / v2.y);
object.position.z = 3;
- cubeCamera(args) {
- // Create cube render target
- const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true } )
- // Create cube camera
- const cubeCamera = new THREE.CubeCamera( 0.1, 500, cubeRenderTarget )
- createObject(args.CAMERA, cubeCamera, args.GROUP)
+ createObject(args.CAMERA, object, args.GROUP);
+ }
+ setCamera(args) {
+ let object = getObject(args.CAMERA);
+ object[args.PROPERTY] = args.VALUE;
+ object.updateProjectionMatrix();
+ }
+ getCamera(args) {
+ let object = getObject(args.CAMERA);
+ const value = JSON.stringify(object[args.PROPERTY]);
+ return value;
+ }
+ renderSceneCamera(args) {
+ let object = getObject(args.CAMERA);
+ if (!object) return;
+ camera = object;
+ //reset composer, else it does not update.
+ composer.passes = [];
+ passes = {};
+ customEffects = [];
+ updateComposers();
+ }
cubeCamera(args) {
// Create cube render target
@@ -1054,8 +1108,31 @@ function resize() {
assets.renderTargets[cubeRenderTarget.texture.uuid] = cubeRenderTarget.texture;
}
- renderTargets[args.RT] = {target: renderTarget, camera: object}
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture
+ renderTarget(args) {
+ let object = getObject(args.CAMERA);
+ const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
+ generateMipmaps: false,
+ });
+
+ renderTargets[args.RT] = {
+ target: renderTarget,
+ camera: object,
+ };
+ assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ }
+ sizeTarget(args) {
+ renderTargets[args.RT].target.setSize(args.W, args.H);
+ }
+ getTarget(args) {
+ const t = renderTargets[args.RT].target.texture;
+ console.log(t, renderTargets[args.RT]);
+ return `renderTargets/${t.uuid}`;
+ }
+ removeTarget(args) {
+ delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
+ renderTargets[args.RT].target.dispose();
+ delete renderTargets[args.RT];
+ }
}
Scratch.extensions.register(new ThreeCameras());
@@ -2112,17 +2189,17 @@ function resize() {
object.castShadow = true;
object.receiveShadow = true;
- createObject(args.OBJECT3D, object, args.GROUP)
- }
- cloneObject(args) {
- let object = getObject(args.OBJECT3D)
- const clone = object.clone(true)
- clone.name
- createObject(args.NAME, clone, args.GROUP)
- }
- setObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- let values = JSON.parse(args.VALUE)
+ createObject(args.OBJECT3D, object, args.GROUP);
+ }
+ cloneObject(args) {
+ let object = getObject(args.OBJECT3D);
+ const clone = object.clone(true);
+ clone.name;
+ createObject(args.NAME, clone, args.GROUP);
+ }
+ setObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ let values = JSON.parse(args.VALUE);
function degToRad(deg) {
return (deg * Math.PI) / 180;
@@ -2190,57 +2267,63 @@ function resize() {
let value = args.VALUE
if (args.PROPERTY === "rotation") value = value * Math.PI / 180
- object[args.PROPERTY][args.X] += value
- }
- */
- getObjectV3(args) {
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let values = vector3ToString(object[args.PROPERTY])
+ object[args.PROPERTY][args.X] += value
+ }
+ */
+ getObjectV3(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let values = vector3ToString(object[args.PROPERTY]);
if (args.PROPERTY === "rotation") {
const toDeg = Math.PI / 180;
values = [values[0] / toDeg, values[1] / toDeg, values[2] / toDeg];
}
- return JSON.stringify(values)
- }
- setObject(args){
- let object = getObject(args.OBJECT3D)
- let value = args.VALUE
- if (args.PROPERTY === "material") {const mat = materials[args.NAME]; if (mat) value = mat; else value = undefined}
- else if (args.PROPERTY === "geometry") {const geo = geometries[args.NAME]; if (geo) value = geo; else value = undefined}
- else value = !!value
-
- if (value == undefined) return //invalid geo/mat
- object[args.PROPERTY] = value
- }
- getObject(args){
- let object = getObject(args.OBJECT3D)
- if (!object) return
- let value
- if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
- else value = object.visible;
-
- return value
- }
- removeObject(args) {
- removeObject(args.OBJECT3D)
- }
- objectE(args) {
- return scene.children.map(o => o.name).includes(args.NAME)
- }
+ return JSON.stringify(values);
+ }
+ setObject(args) {
+ let object = getObject(args.OBJECT3D);
+ let value = args.VALUE;
+ if (args.PROPERTY === "material") {
+ const mat = materials[args.NAME];
+ if (mat) value = mat;
+ else value = undefined;
+ } else if (args.PROPERTY === "geometry") {
+ const geo = geometries[args.NAME];
+ if (geo) value = geo;
+ else value = undefined;
+ } else value = !!value;
+
+ if (value == undefined) return; //invalid geo/mat
+ object[args.PROPERTY] = value;
+ }
+ getObject(args) {
+ let object = getObject(args.OBJECT3D);
+ if (!object) return;
+ let value;
+ if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
+ else value = object.visible;
+
+ return value;
+ }
+ removeObject(args) {
+ removeObject(args.OBJECT3D);
+ }
+ objectE(args) {
+ return scene.children.map((o) => o.name).includes(args.NAME);
+ }
-//defines
- newMaterial(args) {
- if (materials[args.NAME] && alerts) alert ("material already exists! will replace...")
- const mat = new THREE[args.TYPE]();
- mat.name = args.NAME;
+ //defines
+ newMaterial(args) {
+ if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ const mat = new THREE[args.TYPE]();
+ mat.name = args.NAME;
- materials[args.NAME] = mat;
- }
- async setMaterial(args) {
- if (typeof(args.VALUE) == "string" && args.VALUE.at(0) == "|") return
- const mat = materials[args.NAME]
+ materials[args.NAME] = mat;
+ }
+ async setMaterial(args) {
+ if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
+ const mat = materials[args.NAME];
let value = args.VALUE;
@@ -2254,47 +2337,72 @@ function resize() {
console.log("o:", args.VALUE, typeof args.VALUE);
console.log("r:", value, typeof value);
- geometries[args.NAME] = geo
- }
- setGeometry(args) {
- const geo = geometries[args.NAME]
- geo[args.PROPERTY] = (args.VALUE)
+ mat[args.PROPERTY] = await value; //await incase its a texture
+ mat.needsUpdate = true;
+ }
+ setBlending(args) {
+ const mat = materials[args.NAME];
+ mat.blending = THREE[args.VALUE];
+ mat.premultipliedAlpha = true;
+ mat.needsUpdate = true;
+ }
+ setDepth(args) {
+ const mat = materials[args.NAME];
+ mat.depthFunc = THREE[args.VALUE];
+ mat.needsUpdate = true;
+ }
+ removeMaterial(args) {
+ const mat = materials[args.NAME];
+ mat.dispose();
+ delete materials[args.NAME];
+ }
+ materialE(args) {
+ return materials[args.NAME] ? true : false;
+ }
- geo.needsUpdate = true;
- }
- removeGeometry(args){
- const geo = geometries[args.NAME]
- geo.dispose()
- delete(geometries[args.NAME])
- }
- geometryE(args) {
- return geometries[args.NAME] ? true : false
- }
+ newGeometry(args) {
+ if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ const geo = new THREE[args.TYPE]();
+ geo.name = args.NAME;
- newGeo(args) {
- const geometry = new THREE.BufferGeometry()
- geometry.name = args.NAME
- geometries[args.NAME] = geometry
- }
- async geoPoints(args) {
- const geometry = geometries[args.NAME]
- const positions = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v3 of each vertex of each triangle
+ geometries[args.NAME] = geo;
+ }
+ setGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo[args.PROPERTY] = args.VALUE;
- geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3))
- geometry.computeVertexNormals()
+ geo.needsUpdate = true;
+ }
+ removeGeometry(args) {
+ const geo = geometries[args.NAME];
+ geo.dispose();
+ delete geometries[args.NAME];
+ }
+ geometryE(args) {
+ return geometries[args.NAME] ? true : false;
+ }
- geometry.needsUpdate = true
- }
- geoUVs(args) {
- const geometry = geometries[args.NAME]
- const UVs = args.POINTS.split(" ").map(v=>JSON.parse(v)).flat() //array of v2 of each UV of each triangle
+ newGeo(args) {
+ const geometry = new THREE.BufferGeometry();
+ geometry.name = args.NAME;
+ geometries[args.NAME] = geometry;
+ }
+ async geoPoints(args) {
+ const geometry = geometries[args.NAME];
+ const positions = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v3 of each vertex of each triangle
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(positions), 3));
geometry.computeVertexNormals();
- splines(args) {
- const geometry = new THREE.TubeGeometry(getAsset(args.CURVE))
- geometry.name = args.NAME
+ geometry.needsUpdate = true;
+ }
+ geoUVs(args) {
+ const geometry = geometries[args.NAME];
+ const UVs = args.POINTS.split(" ")
+ .map((v) => JSON.parse(v))
+ .flat(); //array of v2 of each UV of each triangle
geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(UVs), 2));
geometry.needsUpdate = true;
@@ -2304,10 +2412,8 @@ function resize() {
const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
geometry.name = args.NAME;
- const curve = getAsset(args.CURVE)
- const spacing = parseFloat(args.SPACING) || 1
- const curveLength = curve.getLength()
- const divisions = Math.floor(curveLength / spacing)
+ geometries[args.NAME] = geometry;
+ }
async splineModel(args) {
const model = await getModel(args.MODEL, args.NAME);
@@ -2329,12 +2435,36 @@ function resize() {
const temp = model.clone(true);
temp.position.copy(pos);
- temp.traverse(child => {
- if (child.isMesh && child.geometry) {
- const geom = child.geometry.clone()
- geom.applyMatrix4(child.matrixWorld)
- geomList.push(geom)
- matList.push(child.material) //.clone() ?
+ const up = new THREE.Vector3(0, 0, 1);
+ const quat = new THREE.Quaternion().setFromUnitVectors(up, tangent.clone().normalize());
+ temp.quaternion.copy(quat);
+
+ temp.updateMatrixWorld(true);
+
+ temp.traverse((child) => {
+ if (child.isMesh && child.geometry) {
+ const geom = child.geometry.clone();
+ geom.applyMatrix4(child.matrixWorld);
+ geomList.push(geom);
+ matList.push(child.material); //.clone() ?
+ }
+ });
+ }
+
+ const validGeoms = geomList.filter((g) => {
+ const ok = g && g.isBufferGeometry && g.attributes && g.attributes.position;
+ if (!ok) console.warn("geometry skipped:", g);
+ return ok;
+ });
+
+ const merged = BufferGeometryUtils.mergeGeometries(validGeoms, true);
+ merged.computeBoundingBox();
+ merged.computeBoundingSphere();
+
+ merged.name = args.NAME;
+ geometries[args.NAME] = merged;
+ matList.name = args.NAME;
+ materials[args.NAME] = matList;
}
async text(args) {
@@ -2362,11 +2492,8 @@ function resize() {
geometry.name = args.NAME;
- const params = {font: font, size: JSON.parse(args.S), height: JSON.parse(args.D), curveSegments: JSON.parse(args.CS), bevelEnabled: false}
- const geometry = new TextGeometry.TextGeometry(args.TEXT, params)
- geometry.computeVertexNormals()
- geometry.center() // optional, recenters the text
-
+ geometries[args.NAME] = geometry;
+ }
async loadFont() {
openFileExplorer(".json").then((files) => {
@@ -2558,18 +2685,11 @@ function resize() {
lights[args.NAME] = light;
if (light.type === "AmbientLight" || "HemisphereLight") return;
- light.shadow.mapSize.width = 4096
- light.shadow.mapSize.height = 2048
-
- if (light.type === "SpotLight") {
- light.decay = 0
- light.shadow.camera.near = 500;
- light.shadow.camera.far = 4000;
- light.shadow.camera.fov = 30;
- }
- light.shadow.needsUpdate = true
- light.needsUpdate = true
- }
+ light.castShadow = true;
+ if (light.type === "PointLight") return;
+ //Directional & Spot Light
+ light.target.position.set(0, 0, 0);
+ scene.add(light.target);
light.pos = new THREE.Vector3(0, 0, 0);
@@ -2586,7 +2706,16 @@ function resize() {
light.needsUpdate = true;
}
- if (light.type === "AmbientLight" || "HemisphereLight") return
+ setLight(args) {
+ const light = lights[args.NAME];
+ if (!args.PROPERTY) return;
+ if (args.PROPERTY === "target") {
+ light.target.position.set(...JSON.parse(args.VALUE)); //vector3
+ light.target.updateMatrixWorld();
+ } else {
+ light[args.PROPERTY] = getAsset(args.VALUE);
+ }
+ light.needsUpdate = true;
if (light.type === "AmbientLight" || "HemisphereLight") return;
@@ -3042,34 +3171,50 @@ function resize() {
return JSON.stringify([180 + THREE.MathUtils.radToDeg(pitch), THREE.MathUtils.radToDeg(yaw), 0]);
}
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newCubeTexture(args) {
- const uris = [encodeCostume(args.COSTUMEX0),encodeCostume(args.COSTUMEX1), encodeCostume(args.COSTUMEY0),encodeCostume(args.COSTUMEY1), encodeCostume(args.COSTUMEZ0),encodeCostume(args.COSTUMEZ1)]
- const normalized = await Promise.all(uris.map(uri => resizeImageToSquare(uri, 256)));
- const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
-
- texture.name = "CubeTexture" + args.COSTUMEX0;
-
- setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y)
- assets.textures[texture.uuid] = texture
- return `textures/${texture.uuid}`
- }
- async newEquirectangularTexture(args) {
- const textureURI = encodeCostume(args.COSTUME)
- const texture = await new THREE.TextureLoader().loadAsync(textureURI);
- texture.name = args.COSTUME
- texture.mapping = THREE.EquirectangularReflectionMapping
+ newColor(args) {
+ const color = new THREE.Color(args.HEX);
+ const uuid = crypto.randomUUID();
+ assets.colors[uuid] = color;
+ return `colors/${uuid}`;
+ }
+ newFog(args) {
+ const fog = new THREE.Fog(args.COLOR, args.NEAR, args.FAR);
+ const uuid = crypto.randomUUID();
+ assets.fogs[uuid] = fog;
+ return `fogs/${uuid}`;
+ }
+ async newTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newCubeTexture(args) {
+ const uris = [
+ encodeCostume(args.COSTUMEX0),
+ encodeCostume(args.COSTUMEX1),
+ encodeCostume(args.COSTUMEY0),
+ encodeCostume(args.COSTUMEY1),
+ encodeCostume(args.COSTUMEZ0),
+ encodeCostume(args.COSTUMEZ1),
+ ];
+ const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
+ const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
texture.name = "CubeTexture" + args.COSTUMEX0;
- curve(args) {
- function parsePoints(input) {
- // Match all [x,y,z] groups
- const matches = input.match(/\[([^\]]+)\]/g)
- if (!matches) return []
+ setTexutre(texture, args.MODE, args.STYLE, args.X, args.Y);
+ assets.textures[texture.uuid] = texture;
+ return `textures/${texture.uuid}`;
+ }
+ async newEquirectangularTexture(args) {
+ const textureURI = encodeCostume(args.COSTUME);
+ const texture = await new THREE.TextureLoader().loadAsync(textureURI);
+ texture.name = args.COSTUME;
+ texture.mapping = THREE.EquirectangularReflectionMapping;
setTexutre(texture, args.MODE);
assets.textures[texture.uuid] = texture;
@@ -3094,8 +3239,20 @@ function resize() {
const curve = new THREE[args.TYPE](points);
curve.closed = JSON.parse(args.CLOSED);
- raycast(args) {
- const origin = new THREE.Vector3(...JSON.parse(args.V3))
+ const uuid = crypto.randomUUID();
+ assets.curves[uuid] = curve;
+ return `curves/${uuid}`;
+ }
+
+ getItem(args) {
+ const items = JSON.parse(args.ARRAY);
+ const item = items[args.ITEM - 1];
+ if (!item) return "0";
+ return item;
+ }
+
+ raycast(args) {
+ const origin = new THREE.Vector3(...JSON.parse(args.V3));
// rotation is in degrees => convert to radians first
const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
@@ -3322,9 +3479,12 @@ function resize() {
async addModel(args) {
const group = await getModel(args.ITEM, args.NAME);
- }
- async addModel(args) {
- const group = await getModel(args.ITEM, args.NAME)
+ createObject(args.NAME, group, args.GROUP);
+ }
+ getModel(args) {
+ if (!models[args.NAME]) return;
+ return Object.keys(models[args.NAME].actions).toString();
+ }
playAnimation(args) {
const model = models[args.NAME];
@@ -3625,26 +3785,31 @@ function resize() {
updateComposers();
}
- const pass = new EffectPass(camera, bloomEffect)
+ bloom(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const bloomEffect = new BloomEffect({
+ intensity: args.I,
+ luminanceThreshold: args.T, // ← correct key
+ luminanceSmoothing: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ bloomEffect.blendMode.opacity.value = args.OP;
const pass = new EffectPass(camera, bloomEffect);
composer.addPass(pass);
}
- const godRays = new GodRaysEffect(camera, sun, {
- resolutionScale: args.RES,
- density: args.DENS, // ray density
- decay: args.DEC, // fade out
- weight: args.WEI, // brightness of rays
- exposure: args.EXP,
- samples: args.SAMP,
- blendFunction: BlendFunction[args.BLEND],
- })
- godRays.blendMode.opacity.value = args.OP
- const pass = new EffectPass(camera, godRays)
- composer.addPass(pass)
- }
+ godRays(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ let object = getObject(args.NAME);
+ const sun = object;
const godRays = new GodRaysEffect(camera, sun, {
resolutionScale: args.RES,
@@ -3660,20 +3825,34 @@ function resize() {
composer.addPass(pass);
}
- depth(args) {
- if (!camera || !scene) {if (alerts) alert("set a camera!"); return}
- const dofEffect = new DepthOfFieldEffect(camera, {
- focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
- focalLength: args.FL, // lens focal length in meters
- bokehScale: args.BS, // strength/size of the blur circles
- height: args.H, // resolution hint (affects quality/perf)
- blendFunction: BlendFunction[args.BLEND],
- })
- dofEffect.blendMode.opacity.value = args.OP
-
- const dofPass = new EffectPass(camera, dofEffect)
- composer.addPass(dofPass)
- }
+ dots(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dot = new DotScreenEffect({
+ angle: args.A,
+ scale: args.S,
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dot.blendMode.opacity.value = args.OP;
+ const pass = new EffectPass(camera, dot);
+ composer.addPass(pass);
+ }
+
+ depth(args) {
+ if (!camera || !scene) {
+ if (alerts) alert("set a camera!");
+ return;
+ }
+ const dofEffect = new DepthOfFieldEffect(camera, {
+ focusDistance: (args.FD - camera.near) / (camera.far - camera.near), // how far from camera things are sharp (0 = near, 1 = far)
+ focalLength: args.FL, // lens focal length in meters
+ bokehScale: args.BS, // strength/size of the blur circles
+ height: args.H, // resolution hint (affects quality/perf)
+ blendFunction: BlendFunction[args.BLEND],
+ });
+ dofEffect.blendMode.opacity.value = args.OP;
const dofPass = new EffectPass(camera, dofEffect);
composer.addPass(dofPass);
@@ -4403,7 +4582,7 @@ function resize() {
};
}
joint(jointData, bodyA, bodyB) {
- physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true)
+ physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
}
fixedJoint(args) {
@@ -4521,57 +4700,54 @@ function resize() {
}
getWorld(args) {
- if (args.PROPERTY === "log") {console.log(physicsWorld); return "logged"}
- return JSON.stringify(physicsWorld[args.PROPERTY])
+ if (args.PROPERTY === "log") {
+ console.log(physicsWorld);
+ return "logged";
+ }
+ return JSON.stringify(physicsWorld[args.PROPERTY]);
}
setRB(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.rigidBody[args.PROPERTY](value)
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.rigidBody[args.PROPERTY](value);
}
setC(args) {
- let value = args.VALUE
- if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE
- let object = getObject(args.OBJECT)
- object.collider[args.PROPERTY](value)
+ let value = args.VALUE;
+ if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
+ let object = getObject(args.OBJECT);
+ object.collider[args.PROPERTY](value);
}
getRB(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.rigidBody[args.PROPERTY]())
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.rigidBody[args.PROPERTY]());
}
getC(args) {
- let object = getObject(args.OBJECT)
- return JSON.stringify(object.collider[args.PROPERTY]())
+ let object = getObject(args.OBJECT);
+ return JSON.stringify(object.collider[args.PROPERTY]());
}
lockObjectAxis(args) {
- let object = getObject(args.OBJECT)
- const x = !JSON.parse(args.X)
- const y = !JSON.parse(args.Y)
- const z = !JSON.parse(args.Z)
- object.rigidBody[args.PROPERTY](x,y,z,true) //changes is xyz, wake up
+ let object = getObject(args.OBJECT);
+ const x = !JSON.parse(args.X);
+ const y = !JSON.parse(args.Y);
+ const z = !JSON.parse(args.Z);
+ object.rigidBody[args.PROPERTY](x, y, z, true); //changes is xyz, wake up
}
objectPhysics(args) {
- let object = getObject(args.OBJECT)
- object.physics = JSON.parse(args.state)
+ let object = getObject(args.OBJECT);
+ object.physics = JSON.parse(args.state);
if (JSON.parse(args.state)) {
//if already exists delete:
if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
- }
-
- if (!physicsWorld) {
- console.error("Physics world not created!");
- return;
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
}
-
/*asing a rigidbody and collider to object and add them to physicsWorld*/
let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
.setTranslation(object.position.x, object.position.y, object.position.z)
@@ -4582,14 +4758,26 @@ function resize() {
z: object.quaternion._z,
});
- let colliderDesc
- switch(args.collider) {
- case "cuboid": colliderDesc = createCuboidCollider(object,); break
- case "ball": colliderDesc = createBallCollider(object); break
- case "convexHull": colliderDesc = createConvexHullCollider(object); break
- case "trimesh": colliderDesc = TriMesh(object); break
- }
- colliderDesc.setSensor(JSON.parse(args.state2)).setMass(args.mass).setDensity(args.density).setFriction(args.friction)
+ let colliderDesc;
+ switch (args.collider) {
+ case "cuboid":
+ colliderDesc = createCuboidCollider(object);
+ break;
+ case "ball":
+ colliderDesc = createBallCollider(object);
+ break;
+ case "convexHull":
+ colliderDesc = createConvexHullCollider(object);
+ break;
+ case "trimesh":
+ colliderDesc = TriMesh(object);
+ break;
+ }
+ colliderDesc
+ .setSensor(JSON.parse(args.state2))
+ .setMass(args.mass)
+ .setDensity(args.density)
+ .setFriction(args.friction);
let rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);
let collider = physicsWorld.createCollider(colliderDesc, rigidBody);
@@ -4598,25 +4786,25 @@ function resize() {
object.collider = collider;
} else {
/*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody)
- object.rigidBody = null
- object.collider = null
+ physicsWorld.removeRigidBody(object.rigidBody);
+ object.rigidBody = null;
+ object.collider = null;
}
}
enableCCD(args) {
- let object = getObject(args.OBJECT)
+ let object = getObject(args.OBJECT);
if (object.physics) {
- let rigidBody = object.rigidBody
- rigidBody.enableCcd(JSON.parse(args.state))
+ let rigidBody = object.rigidBody;
+ rigidBody.enableCcd(JSON.parse(args.state));
}
}
- addForce(args) {
- let object = getObject(args.OBJECT)
- const vector = JSON.parse(args.VALUE).map(Number)
-
- let force = new THREE.Vector3(vector[0],vector[1],vector[2])
+ addForce(args) {
+ let object = getObject(args.OBJECT);
+ const vector = JSON.parse(args.VALUE).map(Number);
+
+ let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
if (args.SPACE === "local") {
force.applyQuaternion(object.quaternion);
}
@@ -4624,14 +4812,14 @@ function resize() {
object.rigidBody[args.PROPERTY](force, true);
}
- resetForces(args) {
- rigidBody[args.PROPERTY](true)
- }
+ resetForces(args) {
+ rigidBody[args.PROPERTY](true);
+ }
- sensorSingle(args) {
- const sensor = getObject(args.SENSOR)
+ sensorSingle(args) {
+ const sensor = getObject(args.SENSOR);
- let object = getObject(args.OBJECT)
+ let object = getObject(args.OBJECT);
let touching = false;
physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
@@ -4641,18 +4829,18 @@ function resize() {
return touching;
}
- sensorAll(args) {
- const sensor = getObject(args.SENSOR)
+ sensorAll(args) {
+ const sensor = getObject(args.SENSOR);
const touchedObjects = [];
- // loop thruogh every collider touching sensor
- physicsWorld.intersectionPairsWith(sensor.collider, otherCollider => {
- // find owner of collider
- const otherObject = scene.children.find(o => o.collider === otherCollider)
- console.log(otherCollider)
- if (otherObject) touchedObjects.push(otherObject.name)
- })
+ // loop thruogh every collider touching sensor
+ physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
+ // find owner of collider
+ const otherObject = scene.children.find((o) => o.collider === otherCollider);
+ console.log(otherCollider);
+ if (otherObject) touchedObjects.push(otherObject.name);
+ });
return JSON.stringify(touchedObjects);
}
From b1b5084d3945846822adfe379127b93b96ee65f3 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 17:12:06 -0600
Subject: [PATCH 16/32] Update threejsD.js
From 38725ca2b700a672b9239374114316d42ad7e8e4 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 17:16:50 -0600
Subject: [PATCH 17/32] fix again
---
threejsD.js | 298 +++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 223 insertions(+), 75 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index bc6db57..4237d0b 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -60,7 +60,6 @@
let physicsWorld;
let threeRenderer;
- let scene;
let camera;
let eulerOrder = "YXZ";
@@ -68,9 +67,8 @@
let passes = {};
let customEffects = [];
let renderTargets = {};
-
- let materials = {};
- let geometries = {};
+ window.__THREE_MATERIALS__ = {};
+ window.__THREE_GEOMETRIES__ = {}
let lights = {};
let models = {};
@@ -87,14 +85,13 @@
function resetor(level) {
camera = undefined;
- composer.reset();
+ if (composer) composer.reset();
passes = {};
customEffects = [];
renderTargets = {};
-
- materials = {};
- geometries = {};
+ window.__THREE_MATERIALS__ = {};
+ window.__THREE_GEOMETRIES__ = {};
lights = {};
models = {};
@@ -129,8 +126,36 @@
return [x, y, z];
}
+ // Helper function to get current scene
+ function getCurrentScene() {
+ const threeScene = runtime.ext_threeScene;
+ if (!threeScene) return null;
+ const currentSceneName = threeScene.currentSceneName;
+ return currentSceneName ? threeScene.scenes[currentSceneName] : null;
+ }
+
+ // Helper function to get scene by name
+ function getSceneByName(sceneName) {
+ const threeScene = runtime.ext_threeScene;
+ if (!threeScene) return null;
+ return threeScene.scenes[sceneName];
+ }
+
+ // Helper function to set current scene
+ function setCurrentScene(sceneName) {
+ const threeScene = runtime.ext_threeScene;
+ if (!threeScene) return;
+ threeScene.currentSceneName = sceneName;
+ }
+
//objects
function createObject(name, content, parentName) {
+ const scene = getCurrentScene();
+ if (!scene) {
+ alerts ? alert("No active scene! Create a scene first!") : null;
+ return;
+ }
+
let object = getObject(name, true);
if (object) {
removeObject(name);
@@ -138,13 +163,25 @@
}
content.name = name;
content.rotation._order = eulerOrder;
- parentName === scene.name ? (object = scene) : (object = getObject(parentName));
+
+ let parentObject;
+ if (parentName === scene.name) {
+ parentObject = scene;
+ } else {
+ parentObject = getObject(parentName);
+ if (!parentObject) {
+ parentObject = scene;
+ }
+ }
+
content.physics = false;
-
- object.add(content);
+ parentObject.add(content);
}
function removeObject(name) {
+ const scene = getCurrentScene();
+ if (!scene) return;
+
let object = getObject(name);
if (!object) return;
@@ -162,15 +199,16 @@
}
function getObject(name, isNew) {
- let object = null;
+ const scene = getCurrentScene();
if (!scene) {
alerts ? alert("Can not get " + name + ". Create a scene first!") : null;
- return;
+ return null;
}
- object = scene.getObjectByName(name);
+
+ let object = scene.getObjectByName(name);
if (!object && !isNew) {
alerts ? alert(name + " does not exist! Add it to scene") : null;
- return;
+ return null;
}
return object;
}
@@ -178,7 +216,10 @@
//materials
function encodeCostume(name) {
if (name.startsWith("data:image/")) return name;
- return Scratch.vm.editingTarget.sprite.costumes.find((c) => c.name === name).asset.encodeDataURI();
+ const editingTarget = vm.editingTarget;
+ if (!editingTarget) return null;
+ const costume = editingTarget.sprite.costumes.find((c) => c.name === name);
+ return costume ? costume.asset.encodeDataURI() : null;
}
function setTexutre(texture, mode, style, x, y) {
@@ -252,6 +293,7 @@
}
//composer
function updateComposers() {
+ const scene = getCurrentScene();
if (!camera || !scene) return; // nothing to do yet
// always recreate the RenderPass to point to the current scene/camera
@@ -447,6 +489,7 @@
//loops/init
function stopLoop() {
if (!running) return;
+ vm.renderer.canvas.style.visibility="visible";
running = false;
if (loopId) {
@@ -458,8 +501,8 @@
async function load() {
if (!THREE) {
// @ts-ignore
- THREE = await import("https://esm.sh/three@0.180.0")
- window._THREE_ = THREE
+ THREE = await import("https://esm.sh/three@0.180.0");
+ window._THREE_ = THREE;
//Addons
GLTFLoader = await import("https://esm.sh/three@0.180.0/examples/jsm/loaders/GLTFLoader.js");
OrbitControls = await import("https://esm.sh/three@0.180.0/examples/jsm/controls/OrbitControls.js");
@@ -502,6 +545,7 @@
stencil: false,
depth: true,
});
+ window.__THREE_RENDERER__ = threeRenderer;
threeRenderer.setPixelRatio(window.devicePixelRatio);
threeRenderer.outputColorSpace = THREE.SRGBColorSpace; // correct colors
threeRenderer.toneMapping = THREE.ACESFilmicToneMapping; // HDR look (test)
@@ -548,22 +592,32 @@
running = false;
load();
- startRenderLoop()
- runtime.on('PROJECT_START', () => startRenderLoop())
- runtime.on('PROJECT_STOP_ALL', () => stopLoop())
- runtime.on('STAGE_SIZE_CHANGED', () => {requestAnimationFrame(() => resize())})
- checkCanvasSize()
+ startRenderLoop();
+ runtime.on("PROJECT_START", () => startRenderLoop());
+ runtime.on("PROJECT_STOP_ALL", () => stopLoop());
+ runtime.on("STAGE_SIZE_CHANGED", () => {
+ requestAnimationFrame(() => resize());
+ });
+ checkCanvasSize();
}
}
function startRenderLoop() {
if (running) return;
+ vm.renderer.canvas.style.visibility="hidden";
running = true;
const loop = () => {
if (!running) return;
+
+ const scene = getCurrentScene();
+ if (!scene) {
+ loopId = requestAnimationFrame(loop);
+ return;
+ }
+
//RAPIER
- if (physicsWorld && scene) {
+ if (physicsWorld) {
physicsWorld.step();
scene.children.forEach((obj) => {
@@ -574,7 +628,7 @@
}
});
}
- if (scene && camera) {
+ if (camera) {
if (controls) controls.update();
const delta = clock.getDelta();
@@ -633,7 +687,7 @@
const h = canvas.height;
threeRenderer.setSize(w, h);
- composer.setSize(w, h);
+ if (composer) composer.setSize(w, h);
customEffects.forEach((e) => {
if (e.uniforms.get("resolution")) {
e.uniforms.get("resolution").value.set(w, h);
@@ -730,8 +784,10 @@
class ThreeScene {
constructor() {
+ // expose threejs and the scenes, so other extensions and javascript can do stuff manually
this.THREE = THREE;
this.scenes = {};
+ this.currentSceneName = null;
}
getInfo() {
@@ -835,36 +891,51 @@
}
newScene(args) {
- scene = new THREE.Scene();
- scene.name = args.NAME;
+ const scene = new THREE.Scene();
+ const sceneName = Scratch.Cast.toString(args.NAME);
+ scene.name = sceneName;
scene.background = new THREE.Color("#222");
- //scene.add(new THREE.GridHelper(16, 16)) //future helper section?
- this.scenes = {
- ...this.scenes,
- ...scene,
- };
+
+ this.scenes[sceneName] = scene;
+ this.currentSceneName = sceneName; // Set as current scene
+
resetor(0);
}
reset() {
resetor(1);
+ this.scenes = {};
+ this.currentSceneName = null;
}
async setSceneProperty(args) {
+ const scene = getCurrentScene();
+ if (!scene) {
+ alerts ? alert("No active scene! Create a scene first!") : null;
+ return;
+ }
+
const property = args.PROPERTY;
const value = getAsset(args.VALUE);
scene[property] = value;
}
+
getSceneObjects(args) {
+ const scene = getCurrentScene();
+ if (!scene) {
+ alerts ? alert("No active scene! Create a scene first!") : null;
+ return "[]";
+ }
+
const names = [];
if (args.THING === "Objects") {
scene.traverse((obj) => {
if (obj.name) names.push(obj.name); //if it has a name, add to list!
});
- } else if (args.THING === "Materials") return JSON.stringify(Object.keys(materials));
- else if (args.THING === "Geometries") return JSON.stringify(Object.keys(geometries));
- else if (args.THING === "Ligts") return JSON.stringify(Object.keys(lights));
+ } else if (args.THING === "Materials") return JSON.stringify(Object.keys(window.__THREE_MATERIALS__));
+ else if (args.THING === "Geometries") return JSON.stringify(Object.keys(window.__THREE_GEOMETRIES__));
+ else if (args.THING === "Lights") return JSON.stringify(Object.keys(lights));
else if (args.THING === "Scene Properties") {
console.log(scene);
return "check console";
@@ -1073,11 +1144,13 @@
}
setCamera(args) {
let object = getObject(args.CAMERA);
+ if (!object) return;
object[args.PROPERTY] = args.VALUE;
object.updateProjectionMatrix();
}
getCamera(args) {
let object = getObject(args.CAMERA);
+ if (!object) return "null";
const value = JSON.stringify(object[args.PROPERTY]);
return value;
}
@@ -1110,6 +1183,8 @@
renderTarget(args) {
let object = getObject(args.CAMERA);
+ if (!object) return;
+
const renderTarget = new THREE.WebGLRenderTarget(360, 360, {
generateMipmaps: false,
});
@@ -1118,19 +1193,25 @@
target: renderTarget,
camera: object,
};
- assets.renderTargets[renderTarget.texture.uuid] == renderTarget.texture;
+ assets.renderTargets[renderTarget.texture.uuid] = renderTarget.texture;
}
sizeTarget(args) {
- renderTargets[args.RT].target.setSize(args.W, args.H);
+ const target = renderTargets[args.RT];
+ if (!target) return;
+ target.target.setSize(args.W, args.H);
}
getTarget(args) {
- const t = renderTargets[args.RT].target.texture;
+ const target = renderTargets[args.RT];
+ if (!target) return "null";
+ const t = target.target.texture;
console.log(t, renderTargets[args.RT]);
return `renderTargets/${t.uuid}`;
}
removeTarget(args) {
- delete assets.renderTargets[renderTargets[args.RT].target.texture.uuid];
- renderTargets[args.RT].target.dispose();
+ const target = renderTargets[args.RT];
+ if (!target) return;
+ delete assets.renderTargets[target.target.texture.uuid];
+ target.target.dispose();
delete renderTargets[args.RT];
}
}
@@ -2193,12 +2274,15 @@
}
cloneObject(args) {
let object = getObject(args.OBJECT3D);
+ if (!object) return;
const clone = object.clone(true);
clone.name;
createObject(args.NAME, clone, args.GROUP);
}
setObjectV3(args) {
let object = getObject(args.OBJECT3D);
+ if (!object) return;
+
let values = JSON.parse(args.VALUE);
function degToRad(deg) {
@@ -2272,7 +2356,7 @@
*/
getObjectV3(args) {
let object = getObject(args.OBJECT3D);
- if (!object) return;
+ if (!object) return "[0,0,0]";
let values = vector3ToString(object[args.PROPERTY]);
if (args.PROPERTY === "rotation") {
const toDeg = Math.PI / 180;
@@ -2283,13 +2367,15 @@
}
setObject(args) {
let object = getObject(args.OBJECT3D);
+ if (!object) return;
+
let value = args.VALUE;
if (args.PROPERTY === "material") {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
if (mat) value = mat;
else value = undefined;
} else if (args.PROPERTY === "geometry") {
- const geo = geometries[args.NAME];
+ const geo = window.__THREE_GEOMETRIES__[args.NAME];
if (geo) value = geo;
else value = undefined;
} else value = !!value;
@@ -2299,7 +2385,7 @@
}
getObject(args) {
let object = getObject(args.OBJECT3D);
- if (!object) return;
+ if (!object) return "null";
let value;
if (args.PROPERTY != "visible") value = object[args.PROPERTY].name;
else value = object.visible;
@@ -2310,20 +2396,23 @@
removeObject(args.OBJECT3D);
}
objectE(args) {
+ const scene = getCurrentScene();
+ if (!scene) return false;
return scene.children.map((o) => o.name).includes(args.NAME);
}
//defines
newMaterial(args) {
- if (materials[args.NAME] && alerts) alert("material already exists! will replace...");
+ if (window.__THREE_MATERIALS__[args.NAME] && alerts) alert("material already exists! will replace...");
const mat = new THREE[args.TYPE]();
mat.name = args.NAME;
- materials[args.NAME] = mat;
+ window.__THREE_MATERIALS__[args.NAME] = mat;
}
async setMaterial(args) {
if (typeof args.VALUE == "string" && args.VALUE.at(0) == "|") return;
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
+ if (!mat) return;
let value = args.VALUE;
@@ -2341,54 +2430,60 @@
mat.needsUpdate = true;
}
setBlending(args) {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
+ if (!mat) return;
mat.blending = THREE[args.VALUE];
mat.premultipliedAlpha = true;
mat.needsUpdate = true;
}
setDepth(args) {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
+ if (!mat) return;
mat.depthFunc = THREE[args.VALUE];
mat.needsUpdate = true;
}
removeMaterial(args) {
- const mat = materials[args.NAME];
+ const mat = window.__THREE_MATERIALS__[args.NAME];
+ if (!mat) return;
mat.dispose();
- delete materials[args.NAME];
+ delete window.__THREE_MATERIALS__[args.NAME];
}
materialE(args) {
- return materials[args.NAME] ? true : false;
+ return window.__THREE_MATERIALS__[args.NAME] ? true : false;
}
newGeometry(args) {
- if (geometries[args.NAME] && alerts) alert("geometry already exists! will replace...");
+ if (window.__THREE_GEOMETRIES__[args.NAME] && alerts) alert("geometry already exists! will replace...");
const geo = new THREE[args.TYPE]();
geo.name = args.NAME;
- geometries[args.NAME] = geo;
+ window.__THREE_GEOMETRIES__[args.NAME] = geo;
}
setGeometry(args) {
- const geo = geometries[args.NAME];
+ const geo = window.__THREE_GEOMETRIES__[args.NAME];
+ if (!geo) return;
geo[args.PROPERTY] = args.VALUE;
geo.needsUpdate = true;
}
removeGeometry(args) {
- const geo = geometries[args.NAME];
+ const geo = window.__THREE_GEOMETRIES__[args.NAME];
+ if (!geo) return;
geo.dispose();
- delete geometries[args.NAME];
+ delete window.__THREE_GEOMETRIES__[args.NAME];
}
geometryE(args) {
- return geometries[args.NAME] ? true : false;
+ return window.__THREE_GEOMETRIES__[args.NAME] ? true : false;
}
newGeo(args) {
const geometry = new THREE.BufferGeometry();
geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
+ window.__THREE_GEOMETRIES__[args.NAME] = geometry;
}
async geoPoints(args) {
- const geometry = geometries[args.NAME];
+ const geometry = window.__THREE_GEOMETRIES__[args.NAME];
+ if (!geometry) return;
const positions = args.POINTS.split(" ")
.map((v) => JSON.parse(v))
.flat(); //array of v3 of each vertex of each triangle
@@ -2399,7 +2494,8 @@
geometry.needsUpdate = true;
}
geoUVs(args) {
- const geometry = geometries[args.NAME];
+ const geometry = window.__THREE_GEOMETRIES__[args.NAME];
+ if (!geometry) return;
const UVs = args.POINTS.split(" ")
.map((v) => JSON.parse(v))
.flat(); //array of v2 of each UV of each triangle
@@ -2412,7 +2508,7 @@
const geometry = new THREE.TubeGeometry(getAsset(args.CURVE));
geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
+ window.__THREE_GEOMETRIES__[args.NAME] = geometry;
}
async splineModel(args) {
@@ -2462,9 +2558,9 @@
merged.computeBoundingSphere();
merged.name = args.NAME;
- geometries[args.NAME] = merged;
+ window.__THREE_GEOMETRIES__[args.NAME] = merged;
matList.name = args.NAME;
- materials[args.NAME] = matList;
+ window.__THREE_MATERIALS__[args.NAME] = matList;
}
async text(args) {
@@ -2492,7 +2588,7 @@
geometry.name = args.NAME;
- geometries[args.NAME] = geometry;
+ window.__THREE_GEOMETRIES__[args.NAME] = geometry;
}
async loadFont() {
@@ -2689,7 +2785,9 @@
if (light.type === "PointLight") return;
//Directional & Spot Light
light.target.position.set(0, 0, 0);
- scene.add(light.target);
+
+ const scene = getCurrentScene();
+ if (scene) scene.add(light.target);
light.pos = new THREE.Vector3(0, 0, 0);
@@ -2708,7 +2806,8 @@
setLight(args) {
const light = lights[args.NAME];
- if (!args.PROPERTY) return;
+ if (!light || !args.PROPERTY) return;
+
if (args.PROPERTY === "target") {
light.target.position.set(...JSON.parse(args.VALUE)); //vector3
light.target.updateMatrixWorld();
@@ -3185,6 +3284,7 @@
}
async newTexture(args) {
const textureURI = encodeCostume(args.COSTUME);
+ if (!textureURI) return "null";
const texture = await new THREE.TextureLoader().loadAsync(textureURI);
texture.name = args.COSTUME;
@@ -3201,6 +3301,9 @@
encodeCostume(args.COSTUMEZ0),
encodeCostume(args.COSTUMEZ1),
];
+ // Check if all URIs are valid
+ if (uris.some(uri => !uri)) return "null";
+
const normalized = await Promise.all(uris.map((uri) => resizeImageToSquare(uri, 256)));
const texture = await new THREE.CubeTextureLoader().loadAsync(normalized);
@@ -3212,6 +3315,7 @@
}
async newEquirectangularTexture(args) {
const textureURI = encodeCostume(args.COSTUME);
+ if (!textureURI) return "null";
const texture = await new THREE.TextureLoader().loadAsync(textureURI);
texture.name = args.COSTUME;
texture.mapping = THREE.EquirectangularReflectionMapping;
@@ -3252,6 +3356,9 @@
}
raycast(args) {
+ const scene = getCurrentScene();
+ if (!scene) return;
+
const origin = new THREE.Vector3(...JSON.parse(args.V3));
// rotation is in degrees => convert to radians first
const rot = JSON.parse(args.D3).map((v) => (v * Math.PI) / 180);
@@ -3482,7 +3589,7 @@
createObject(args.NAME, group, args.GROUP);
}
getModel(args) {
- if (!models[args.NAME]) return;
+ if (!models[args.NAME]) return "null";
return Object.keys(models[args.NAME].actions).toString();
}
@@ -3786,6 +3893,7 @@
}
bloom(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
@@ -3804,11 +3912,13 @@
}
godRays(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
}
let object = getObject(args.NAME);
+ if (!object) return;
const sun = object;
const godRays = new GodRaysEffect(camera, sun, {
@@ -3826,6 +3936,7 @@
}
dots(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
@@ -3841,6 +3952,7 @@
}
depth(args) {
+ const scene = getCurrentScene();
if (!camera || !scene) {
if (alerts) alert("set a camera!");
return;
@@ -4582,6 +4694,7 @@
};
}
joint(jointData, bodyA, bodyB) {
+ if (!physicsWorld || !bodyA || !bodyB) return;
physicsWorld.createImpulseJoint(jointData, bodyA.rigidBody, bodyB.rigidBody, true);
}
@@ -4700,6 +4813,7 @@
}
getWorld(args) {
+ if (!physicsWorld) return "null";
if (args.PROPERTY === "log") {
console.log(physicsWorld);
return "logged";
@@ -4711,26 +4825,32 @@
let value = args.VALUE;
if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
object.rigidBody[args.PROPERTY](value);
}
setC(args) {
let value = args.VALUE;
if (args.VALUE === "true" || args.VALUE === "false") value = !!args.VALUE;
let object = getObject(args.OBJECT);
+ if (!object || !object.collider) return;
object.collider[args.PROPERTY](value);
}
getRB(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return "null";
return JSON.stringify(object.rigidBody[args.PROPERTY]());
}
getC(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.collider) return "null";
return JSON.stringify(object.collider[args.PROPERTY]());
}
lockObjectAxis(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
+
const x = !JSON.parse(args.X);
const y = !JSON.parse(args.Y);
const z = !JSON.parse(args.Z);
@@ -4739,15 +4859,25 @@
objectPhysics(args) {
let object = getObject(args.OBJECT);
+ if (!object) return;
+
object.physics = JSON.parse(args.state);
if (JSON.parse(args.state)) {
//if already exists delete:
if (object.rigidBody) {
- physicsWorld.removeRigidBody(object.rigidBody);
+ if (physicsWorld) {
+ physicsWorld.removeRigidBody(object.rigidBody);
+ }
object.rigidBody = null;
object.collider = null;
}
+
+ if (!physicsWorld) {
+ console.error("Physics world not created!");
+ return;
+ }
+
/*asing a rigidbody and collider to object and add them to physicsWorld*/
let rigidBodyDesc = RAPIER.RigidBodyDesc[args.type]()
.setTranslation(object.position.x, object.position.y, object.position.z)
@@ -4772,7 +4902,13 @@
case "trimesh":
colliderDesc = TriMesh(object);
break;
+ default:
+ console.error("Unknown collider type:", args.collider);
+ return;
}
+
+ if (!colliderDesc) return;
+
colliderDesc
.setSensor(JSON.parse(args.state2))
.setMass(args.mass)
@@ -4786,7 +4922,9 @@
object.collider = collider;
} else {
/*if disabling physics, delete rigidbody and collider from physicsWorld and object*/
- physicsWorld.removeRigidBody(object.rigidBody);
+ if (physicsWorld && object.rigidBody) {
+ physicsWorld.removeRigidBody(object.rigidBody);
+ }
object.rigidBody = null;
object.collider = null;
}
@@ -4794,7 +4932,7 @@
enableCCD(args) {
let object = getObject(args.OBJECT);
- if (object.physics) {
+ if (object && object.physics && object.rigidBody) {
let rigidBody = object.rigidBody;
rigidBody.enableCcd(JSON.parse(args.state));
}
@@ -4802,6 +4940,8 @@
addForce(args) {
let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
+
const vector = JSON.parse(args.VALUE).map(Number);
let force = new THREE.Vector3(vector[0], vector[1], vector[2]);
@@ -4813,13 +4953,18 @@
}
resetForces(args) {
- rigidBody[args.PROPERTY](true);
+ let object = getObject(args.OBJECT);
+ if (!object || !object.rigidBody) return;
+
+ object.rigidBody[args.PROPERTY](true);
}
sensorSingle(args) {
const sensor = getObject(args.SENSOR);
+ if (!sensor || !sensor.collider || !physicsWorld) return false;
let object = getObject(args.OBJECT);
+ if (!object || !object.collider) return false;
let touching = false;
physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
@@ -4831,14 +4976,17 @@
sensorAll(args) {
const sensor = getObject(args.SENSOR);
+ if (!sensor || !sensor.collider || !physicsWorld) return "[]";
const touchedObjects = [];
- // loop thruogh every collider touching sensor
+ // loop through every collider touching sensor
physicsWorld.intersectionPairsWith(sensor.collider, (otherCollider) => {
// find owner of collider
+ const scene = getCurrentScene();
+ if (!scene) return;
+
const otherObject = scene.children.find((o) => o.collider === otherCollider);
- console.log(otherCollider);
if (otherObject) touchedObjects.push(otherObject.name);
});
From b1ecda16140b7289d0a429a0398c745fb891b1d6 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 17:28:17 -0600
Subject: [PATCH 18/32] test
---
threejsD.js | 147 ++++++++++++++++++++++++++++++----------------------
1 file changed, 84 insertions(+), 63 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 4237d0b..9ea3aeb 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -602,86 +602,107 @@
}
}
- function startRenderLoop() {
- if (running) return;
- vm.renderer.canvas.style.visibility="hidden";
- running = true;
-
- const loop = () => {
- if (!running) return;
-
- const scene = getCurrentScene();
+ async function startRenderLoop() {
+ if (running) return
+ vm.renderer.canvas.style.visibility = "hidden"
+ running = true
+
+ const fixedDt = 1 / 60
+ let accumulator = 0
+ let lastTime = performance.now()
+
+ const yieldToBrowser = () => new Promise(r => setTimeout(r, 0))
+
+ const loop = async (now) => {
+ if (!running) return
+
+ const scene = getCurrentScene()
if (!scene) {
- loopId = requestAnimationFrame(loop);
- return;
+ loopId = requestAnimationFrame(loop)
+ return
}
-
- //RAPIER
+
+ /* ---------- FIXED PHYSICS ---------- */
+ const deltaTime = Math.min(0.1, (now - lastTime) / 1000)
+ lastTime = now
+ accumulator += deltaTime
+
if (physicsWorld) {
- physicsWorld.step();
-
+ while (accumulator >= fixedDt) {
+ physicsWorld.step()
+ accumulator -= fixedDt
+ }
+
scene.children.forEach((obj) => {
- if (!obj.isMesh || !obj.physics) return;
- if (obj.rigidBody) {
- obj.position.copy(obj.rigidBody.translation());
- obj.quaternion.copy(obj.rigidBody.rotation());
- }
- });
+ if (!obj.isMesh || !obj.physics || !obj.rigidBody) return
+ obj.position.copy(obj.rigidBody.translation())
+ obj.quaternion.copy(obj.rigidBody.rotation())
+ })
}
+
+ /* ---------- ASYNC YIELD ---------- */
+ await yieldToBrowser()
+
+ /* ---------- NORMAL UPDATE ---------- */
if (camera) {
- if (controls) controls.update();
-
- const delta = clock.getDelta();
+ if (controls) controls.update()
+
+ const delta = clock.getDelta()
+
Object.values(models).forEach((model) => {
- if (model) model.mixer.update(delta);
- });
-
- Object.values(lights).forEach((light) => updateShadowFrustum(light, camera.position));
-
- //update custom effects time
+ if (model) model.mixer.update(delta)
+ })
+
+ Object.values(lights).forEach((light) =>
+ updateShadowFrustum(light, camera.position)
+ )
+
customEffects.forEach((e) => {
if (e.uniforms.get("time")) {
- e.uniforms.get("time").value += delta;
+ e.uniforms.get("time").value += delta
}
- });
+ })
+
Object.values(renderTargets).forEach((t) => {
- if (t.camera.type == "PerspectiveCamera") {
- t.camera.aspect = t.target.width / t.target.height;
- t.camera.updateProjectionMatrix();
+ if (t.camera.type === "PerspectiveCamera") {
+ t.camera.aspect = t.target.width / t.target.height
+ t.camera.updateProjectionMatrix()
}
- // get meshes using the texture associated with this target
- const displayMeshes = getMeshesUsingTexture(scene, t.target.texture);
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = false;
- });
-
- if (t.camera.type == "PerspectiveCamera") {
- threeRenderer.setRenderTarget(t.target);
- threeRenderer.clear(true, true, true);
- threeRenderer.render(scene, t.camera);
+
+ // ✅ CACHE mesh lookup
+ if (!t._displayMeshes) {
+ t._displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
+ }
+
+ t._displayMeshes.forEach((mesh) => (mesh.visible = false))
+
+ if (t.camera.type === "PerspectiveCamera") {
+ threeRenderer.setRenderTarget(t.target)
+ threeRenderer.clear(true, true, true)
+ threeRenderer.render(scene, t.camera)
} else {
- t.target.clear(threeRenderer);
- t.camera.update(threeRenderer, scene); //cubeCamera
+ t.target.clear(threeRenderer)
+ t.camera.update(threeRenderer, scene)
}
-
- displayMeshes.forEach((mesh) => {
- mesh.visible = true;
- });
- });
-
- camera.aspect = threeRenderer.domElement.width / threeRenderer.domElement.height;
- camera.updateProjectionMatrix();
- threeRenderer.setRenderTarget(null);
- composer.render(delta);
+
+ t._displayMeshes.forEach((mesh) => (mesh.visible = true))
+ })
+
+ camera.aspect =
+ threeRenderer.domElement.width / threeRenderer.domElement.height
+ camera.updateProjectionMatrix()
+
+ threeRenderer.setRenderTarget(null)
+ composer.render(delta)
}
-
- loopId = requestAnimationFrame(loop);
- };
-
- loopId = requestAnimationFrame(loop);
+
+ loopId = requestAnimationFrame(loop)
+ }
+
+ loopId = requestAnimationFrame(loop)
}
+
function resize() {
const w = canvas.width;
const h = canvas.height;
From e762febbca8c256b45566f3c982de83b78be39f6 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 17:50:31 -0600
Subject: [PATCH 19/32] Update threejsD.js
---
threejsD.js | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 9ea3aeb..b1d7c1a 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -489,6 +489,7 @@
//loops/init
function stopLoop() {
if (!running) return;
+ checkCanvasSize();
vm.renderer.canvas.style.visibility="visible";
running = false;
@@ -611,9 +612,7 @@
let accumulator = 0
let lastTime = performance.now()
- const yieldToBrowser = () => new Promise(r => setTimeout(r, 0))
-
- const loop = async (now) => {
+ const loop = (now) => {
if (!running) return
const scene = getCurrentScene()
@@ -622,11 +621,11 @@
return
}
- /* ---------- FIXED PHYSICS ---------- */
- const deltaTime = Math.min(0.1, (now - lastTime) / 1000)
+ const deltaTime = Math.min(0.25, (now - lastTime) / 1000)
lastTime = now
accumulator += deltaTime
+ /* ---------- FIXED PHYSICS ---------- */
if (physicsWorld) {
while (accumulator >= fixedDt) {
physicsWorld.step()
@@ -635,14 +634,16 @@
scene.children.forEach((obj) => {
if (!obj.isMesh || !obj.physics || !obj.rigidBody) return
+
+ // sync transform
obj.position.copy(obj.rigidBody.translation())
obj.quaternion.copy(obj.rigidBody.rotation())
+
+ // allow this body to sleep
+ obj.rigidBody.setCanSleep(true)
})
}
- /* ---------- ASYNC YIELD ---------- */
- await yieldToBrowser()
-
/* ---------- NORMAL UPDATE ---------- */
if (camera) {
if (controls) controls.update()
@@ -650,7 +651,7 @@
const delta = clock.getDelta()
Object.values(models).forEach((model) => {
- if (model) model.mixer.update(delta)
+ model?.mixer?.update(delta)
})
Object.values(lights).forEach((light) =>
@@ -669,7 +670,6 @@
t.camera.updateProjectionMatrix()
}
- // ✅ CACHE mesh lookup
if (!t._displayMeshes) {
t._displayMeshes = getMeshesUsingTexture(scene, t.target.texture)
}
@@ -702,7 +702,6 @@
loopId = requestAnimationFrame(loop)
}
-
function resize() {
const w = canvas.width;
const h = canvas.height;
From 04077a3892787b8f67f480c26c6345a7143a5521 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 17:57:01 -0600
Subject: [PATCH 20/32] Update threejsD.js
---
threejsD.js | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index b1d7c1a..a46fa1a 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -638,9 +638,6 @@
// sync transform
obj.position.copy(obj.rigidBody.translation())
obj.quaternion.copy(obj.rigidBody.rotation())
-
- // allow this body to sleep
- obj.rigidBody.setCanSleep(true)
})
}
@@ -4906,7 +4903,8 @@
x: object.quaternion._x,
y: object.quaternion._y,
z: object.quaternion._z,
- });
+ })
+ .setCanSleep(true);
let colliderDesc;
switch (args.collider) {
From 9e97a4c6079b589aa385471205fc6573546a8de7 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Tue, 16 Dec 2025 18:03:59 -0600
Subject: [PATCH 21/32] Update threejsD.js
---
threejsD.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/threejsD.js b/threejsD.js
index a46fa1a..e1eee1e 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -4904,7 +4904,7 @@
y: object.quaternion._y,
z: object.quaternion._z,
})
- .setCanSleep(true);
+ .setCanSleep(false);
let colliderDesc;
switch (args.collider) {
From b3e62fdb2c9385716663611fb114bf29e1857ea7 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 08:54:06 -0600
Subject: [PATCH 22/32] hopefully fix tw
---
threejsD.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index e1eee1e..8aa6276 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -128,7 +128,7 @@
// Helper function to get current scene
function getCurrentScene() {
- const threeScene = runtime.ext_threeScene;
+ const threeScene = runtime.ext_threeScene || window.__threeScene__;
if (!threeScene) return null;
const currentSceneName = threeScene.currentSceneName;
return currentSceneName ? threeScene.scenes[currentSceneName] : null;
@@ -136,14 +136,14 @@
// Helper function to get scene by name
function getSceneByName(sceneName) {
- const threeScene = runtime.ext_threeScene;
+ const threeScene = runtime.ext_threeScene || window.__threeScene__;
if (!threeScene) return null;
return threeScene.scenes[sceneName];
}
// Helper function to set current scene
function setCurrentScene(sceneName) {
- const threeScene = runtime.ext_threeScene;
+ const threeScene = runtime.ext_threeScene || window.__threeScene__;
if (!threeScene) return;
threeScene.currentSceneName = sceneName;
}
@@ -961,7 +961,8 @@
return JSON.stringify(names); // if objects
}
}
- Scratch.extensions.register(new ThreeScene());
+ window.__threeScene__ = new ThreeScene();
+ Scratch.extensions.register(window.__threeScene__);
class ThreeCameras {
getInfo() {
From ad2b1af54bcdbe1c3f54902801ff055e1c2033e3 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 08:58:29 -0600
Subject: [PATCH 23/32] Update threejsD.js
---
threejsD.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/threejsD.js b/threejsD.js
index 8aa6276..7c82609 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -491,6 +491,14 @@
if (!running) return;
checkCanvasSize();
vm.renderer.canvas.style.visibility="visible";
+ // borrowed from penguinmod's runtime core extension
+ RGB = "ffffff"
+ this.runtime.renderer.setBackgroundColor(
+ parseInt(RGB.slice(0, 2), 16) / 255,
+ parseInt(RGB.slice(2, 4), 16) / 255,
+ parseInt(RGB.slice(4, 6), 16) / 255,
+ RGB.length === 8 ? parseInt(RGB.slice(6, 8), 16) / 255 : 1
+ )
running = false;
if (loopId) {
From 378bfb6d46697fff58e685ff42a725451a132d9e Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 08:59:34 -0600
Subject: [PATCH 24/32] Update threejsD.js
---
threejsD.js | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 7c82609..973a3fe 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -491,14 +491,7 @@
if (!running) return;
checkCanvasSize();
vm.renderer.canvas.style.visibility="visible";
- // borrowed from penguinmod's runtime core extension
- RGB = "ffffff"
- this.runtime.renderer.setBackgroundColor(
- parseInt(RGB.slice(0, 2), 16) / 255,
- parseInt(RGB.slice(2, 4), 16) / 255,
- parseInt(RGB.slice(4, 6), 16) / 255,
- RGB.length === 8 ? parseInt(RGB.slice(6, 8), 16) / 255 : 1
- )
+ renderer.setBackgroundColor(255, 255, 255)
running = false;
if (loopId) {
From ab4f1c27528c794c57590b49fee62f89d96a77a5 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 09:01:37 -0600
Subject: [PATCH 25/32] Update threejsD.js
---
threejsD.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 973a3fe..a024f3f 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -491,7 +491,6 @@
if (!running) return;
checkCanvasSize();
vm.renderer.canvas.style.visibility="visible";
- renderer.setBackgroundColor(255, 255, 255)
running = false;
if (loopId) {
@@ -567,7 +566,7 @@
renderer.addOverlay(threeRenderer.domElement, "manual");
renderer.addOverlay(canvas, "manual");
- renderer.setBackgroundColor(1, 1, 1, 0);
+ renderer.setBackgroundColor(1, 1, 1);
resize();
From 984e920aaef0060108a80428f0ebf58ae07ac3a9 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 11:23:47 -0600
Subject: [PATCH 26/32] Update threejsD.js
---
threejsD.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/threejsD.js b/threejsD.js
index a024f3f..ba9b857 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -599,6 +599,9 @@
runtime.on("STAGE_SIZE_CHANGED", () => {
requestAnimationFrame(() => resize());
});
+ new ResizeObserver(() => {
+ if (running) vm.renderer.canvas.style.visibility = "hidden";
+ }).observe(canvas);
checkCanvasSize();
}
}
From e15243409c1514123b73c4203d255f90dc0ed445 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 11:33:57 -0600
Subject: [PATCH 27/32] Update threejsD.js
---
threejsD.js | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index ba9b857..cc59747 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -599,9 +599,6 @@
runtime.on("STAGE_SIZE_CHANGED", () => {
requestAnimationFrame(() => resize());
});
- new ResizeObserver(() => {
- if (running) vm.renderer.canvas.style.visibility = "hidden";
- }).observe(canvas);
checkCanvasSize();
}
}
@@ -609,6 +606,7 @@
async function startRenderLoop() {
if (running) return
vm.renderer.canvas.style.visibility = "hidden"
+ checkCanvasSize();
running = true
const fixedDt = 1 / 60
From 2152f9ea61694894356a8522086f34179c3c4926 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 11:36:32 -0600
Subject: [PATCH 28/32] Update threejsD.js
---
threejsD.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/threejsD.js b/threejsD.js
index cc59747..b3c525e 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -716,6 +716,7 @@
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
+ if (running) vm.renderer.canvas.style.visibility = "hidden";
}
//wait until all packages are loaded
Promise.resolve(load()).then(() => {
From 23a4588cab5ef941ca24e2b592d71c1604b031c7 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 11:38:17 -0600
Subject: [PATCH 29/32] Update threejsD.js
---
threejsD.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/threejsD.js b/threejsD.js
index b3c525e..a763e40 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -716,7 +716,11 @@
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
- if (running) vm.renderer.canvas.style.visibility = "hidden";
+ if (running) {
+ vm.renderer.canvas.style.visibility = "hidden"
+ } else {
+ vm.renderer.canvas.style.visibility = "visible"
+ }
}
//wait until all packages are loaded
Promise.resolve(load()).then(() => {
From 657698fd94900083c4376882160a7dbff27bdb70 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 11:53:22 -0600
Subject: [PATCH 30/32] Update threejsD.js
---
threejsD.js | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index a763e40..59fdcaa 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -490,7 +490,8 @@
function stopLoop() {
if (!running) return;
checkCanvasSize();
- vm.renderer.canvas.style.visibility="visible";
+ //vm.renderer.canvas.style.visibility="visible";
+ renderer.setBackgroundColor(1, 1, 1);
running = false;
if (loopId) {
@@ -566,7 +567,7 @@
renderer.addOverlay(threeRenderer.domElement, "manual");
renderer.addOverlay(canvas, "manual");
- renderer.setBackgroundColor(1, 1, 1);
+ //renderer.setBackgroundColor(1, 1, 1, 0);
resize();
@@ -600,12 +601,14 @@
requestAnimationFrame(() => resize());
});
checkCanvasSize();
+ renderer.setBackgroundColor(1, 1, 1);
}
}
async function startRenderLoop() {
if (running) return
- vm.renderer.canvas.style.visibility = "hidden"
+ //vm.renderer.canvas.style.visibility = "hidden"
+ renderer.setBackgroundColor(1, 1, 1, 0);
checkCanvasSize();
running = true
@@ -717,9 +720,9 @@
camera.updateProjectionMatrix();
}
if (running) {
- vm.renderer.canvas.style.visibility = "hidden"
+ //vm.renderer.canvas.style.visibility = "hidden"
} else {
- vm.renderer.canvas.style.visibility = "visible"
+ //vm.renderer.canvas.style.visibility = "visible"
}
}
//wait until all packages are loaded
From 929657c41f9b601d1938ad89922df3a9224ab371 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 12:31:02 -0600
Subject: [PATCH 31/32] Update threejsD.js
---
threejsD.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 59fdcaa..0b3f6e4 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -489,9 +489,10 @@
//loops/init
function stopLoop() {
if (!running) return;
- checkCanvasSize();
+ //checkCanvasSize();
//vm.renderer.canvas.style.visibility="visible";
renderer.setBackgroundColor(1, 1, 1);
+ threeRenderer.domElement.style.visibility="hidden"
running = false;
if (loopId) {
@@ -609,7 +610,8 @@
if (running) return
//vm.renderer.canvas.style.visibility = "hidden"
renderer.setBackgroundColor(1, 1, 1, 0);
- checkCanvasSize();
+ threeRenderer.domElement.style.visibility="visible"
+ //checkCanvasSize();
running = true
const fixedDt = 1 / 60
From 0b2040c73dd5c828704d7cbad8772490419919c5 Mon Sep 17 00:00:00 2001
From: FreshPenguin112 <93781766+FreshPenguin112@users.noreply.github.com>
Date: Wed, 17 Dec 2025 12:42:45 -0600
Subject: [PATCH 32/32] Update threejsD.js
---
threejsD.js | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/threejsD.js b/threejsD.js
index 0b3f6e4..e068eb7 100644
--- a/threejsD.js
+++ b/threejsD.js
@@ -80,7 +80,8 @@
curves: {},
renderTargets: {}, //not the same as the global one! this one only stores textures
};
-
+ let rect;
+
let raycastResult = [];
function resetor(level) {
@@ -256,7 +257,7 @@
ctx.drawImage(img, 0, 0, size, size);
resolve(canvas.toDataURL()); // return normalized Data URI
- //delete canvas?
+ canvas.remove();
};
img.src = uri;
});
@@ -727,6 +728,8 @@
//vm.renderer.canvas.style.visibility = "visible"
}
}
+ rect = threeRenderer.domElement.getBoundingClientRect()
+
//wait until all packages are loaded
Promise.resolve(load()).then(() => {
class threejsExtension {
@@ -5030,11 +5033,6 @@
let isLocked = false;
let isPointerLockEnabled = false;
- let rect = threeRenderer.domElement.getBoundingClientRect();
- document.addEventListener("resize", () => {
- rect = threeRenderer.domElement.getBoundingClientRect();
- });
-
const postMouseData = (e, isDown) => {
const {
movementX,