soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
36.4 kB
const Cast = require('../../util/cast');
const Clone = require('../../util/clone');
const ExtensionInfo = require("./info");
const Three = require("three");
const BufferGeometryUtils = require('three/examples/jsm/utils/BufferGeometryUtils');
const { ConvexGeometry } = require('three/examples/jsm/geometries/ConvexGeometry');
const { OBJLoader } = require('three/examples/jsm/loaders/OBJLoader.js');
const { GLTFLoader } = require('three/examples/jsm/loaders/GLTFLoader.js');
const { FBXLoader } = require('three/examples/jsm/loaders/FBXLoader.js');
const Color = require('../../util/color');
const MeshLoaders = {
OBJ: new OBJLoader(),
GLTF: new GLTFLoader(),
FBX: new FBXLoader(),
}
function toRad(deg) {
return deg * (Math.PI / 180);
}
function toDeg(rad) {
return rad * (180 / Math.PI);
}
function normalize(vec) {
const length = Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
return new Three.Vector3(vec.x / length, vec.y / length, vec.z / length);
}
function toDegRounding(rad) {
const result = toDeg(rad);
if (!String(result).includes('.')) return result;
const split = String(result).split('.');
const endingDecimals = split[1].substring(0, 3);
if ((endingDecimals === '999') && (split[1].charAt(3) === '9')) return Number(split[0]) + 1;
return Number(split[0] + '.' + endingDecimals);
}
/**
* Class for 3D blocks
* @constructor
*/
class Jg3DBlocks {
constructor(runtime) {
/**
* The runtime instantiating this block package.
* @type {Runtime}
*/
this.runtime = runtime;
this.three = Three;
// expose addons and that for the funnis
this.BufferGeometryUtils = BufferGeometryUtils;
this.ConvexGeometry = ConvexGeometry;
this.OBJLoader = OBJLoader;
this.GLTFLoader = GLTFLoader;
this.FBXLoader = FBXLoader;
// prism has screenshots, lets tell it to use OUR canvas for them
this.runtime.prism_screenshot_checkForExternalCanvas = true;
this.runtime.prism_screenshot_externalCanvas = null;
// Three.js requirements
/**
* @type {Three.Scene}
*/
this.scene = null;
/**
* @type {Three.Camera}
*/
this.camera = null;
/**
* @type {Three.WebGLRenderer}
*/
this.renderer = null;
this.existingSceneObjects = [];
this.existingSceneLights = [];
// extras
this.lastStageSizeWhenRendering = {
width: 0,
height: 0
}
this.savedMeshes = {};
this.sceneLayer = "front";
this.lastStageColor = [255, 255, 255, 0];
// event recievers
// stop button clicked or project restarted, dispose of all objects
this.runtime.on('PROJECT_STOP_ALL', () => {
this.dispose();
this.sceneLayer = "front";
this.updateScratchCanvasRelayering();
});
}
/**
* Dispose of the scene, camera & renderer (and any objects)
*/
dispose() {
this.existingSceneObjects = [];
this.existingSceneLights = [];
if (this.scene) {
this.scene.remove();
this.scene = null;
}
if (this.camera) {
this.camera.remove();
this.camera = null;
}
if (this.renderer) {
if (this.renderer.domElement) {
this.renderer.domElement.remove();
}
this.renderer.dispose();
this.renderer = null;
this.runtime.prism_screenshot_externalCanvas = null;
}
}
/**
* Displays a message for stack blocks.
* @param {BlockUtility} util
*/
stackWarning(util, message) {
if (!util) return;
if (!util.thread) return;
if (!util.thread.stackClick) return;
const block = util.thread.blockGlowInFrame;
this.runtime.visualReport(block, message);
}
/**
* @returns {object} metadata for this extension and its blocks.
*/
getInfo() {
return ExtensionInfo;
}
// utilities
getScratchCanvas() {
return this.runtime.renderer.canvas;
}
restyleExternalCanvas(canvas) {
canvas.style.position = "absolute"; // position above canvas without pushing it down
canvas.style.width = "100%";
canvas.style.height = "100%";
// we have no reason to register clicks on the three.js canvas,
// so make it click on the scratch canvas instead
canvas.style.pointerEvents = "none";
}
appendElementAboveScratchCanvas(element) {
element.style.zIndex = 450;
if (this.sceneLayer === 'back') {
element.style.zIndex = 0;
}
this.getScratchCanvas().parentElement.prepend(element);
}
updateScratchCanvasRelayering() {
const canvas = this.getScratchCanvas();
canvas.style.backgroundColor = "transparent";
canvas.style.position = "relative"; // allows zIndex changes
if (Cast.toNumber(canvas.style.zIndex) < 1) {
canvas.style.zIndex = 1;
}
// _backgroundColor4f[3] controls opacity
let lastOpacity = this.runtime.renderer._backgroundColor4f[3];
if (this.sceneLayer === 'front') {
this.runtime.renderer.setBackgroundColor(
this.lastStageColor[0],
this.lastStageColor[1],
this.lastStageColor[2],
1
);
}
if (this.sceneLayer === 'back') {
if (
this.runtime.renderer._backgroundColor4f[0] !== this.lastStageColor[0]
|| this.runtime.renderer._backgroundColor4f[1] !== this.lastStageColor[1]
|| this.runtime.renderer._backgroundColor4f[2] !== this.lastStageColor[2]
) {
// color likely changed to sum else
console.log("updated stage color");
this.lastStageColor = this.runtime.renderer._backgroundColor4f;
}
this.runtime.renderer.setBackgroundColor(0, 0, 0, 0);
}
// update if changed
if (lastOpacity !== this.runtime.renderer._backgroundColor4f[3]) {
this.runtime.renderer.dirty = true;
}
}
needsToResizeCanvas() {
const stage = {
width: this.runtime.stageWidth,
height: this.runtime.stageHeight
}
return stage !== this.lastStageSizeWhenRendering;
}
mergeVertices(geometry) {
const vertices = geometry.attributes.position.array;
const vertexMap = {};
const mergedVertices = [];
const newIndices = [];
let newIndex = 0;
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i];
const y = vertices[i + 1];
const z = vertices[i + 2];
const key = `${x},${y},${z}`;
if (vertexMap[key] === undefined) {
vertexMap[key] = newIndex;
mergedVertices.push(x, y, z);
newIndices.push(newIndex);
newIndex++;
} else {
newIndices.push(vertexMap[key]);
}
}
geometry.setAttribute('position', new Three.Float32BufferAttribute(mergedVertices, 3));
geometry.setIndex(new Three.Uint32BufferAttribute(newIndices, 1));
return geometry;
}
performRaycast(raycaster, object) {
const geometry = object.geometry;
const mergedGeometry = this.mergeVertices(geometry);
const boundingGeometry = new Three.BufferGeometry().copy(mergedGeometry);
boundingGeometry.computeBoundingBox();
boundingGeometry.boundingBox.applyMatrix4(object.matrixWorld);
const intersection = raycaster.intersectObject(object, true);
return intersection.length > 0;
}
initialize() {
// dispose of the previous scene
this.dispose();
this.scene = new Three.Scene();
this.renderer = new Three.WebGLRenderer({ preserveDrawingBuffer: true, alpha: true });
this.renderer.penguinMod = {
backgroundColor: 0x000000,
backgroundOpacity: 1
}
this.renderer.setClearColor(0x000000, 1);
// add renderer canvas ontop of scratch canvas
const canvas = this.renderer.domElement;
this.runtime.prism_screenshot_externalCanvas = canvas;
this.restyleExternalCanvas(canvas);
this.appendElementAboveScratchCanvas(canvas);
this.updateScratchCanvasRelayering();
/* dev: test rendering by drawing a cube and see if it appears
// const geometry = new Three.BoxGeometry(1, 1, 1);
// const material = new Three.MeshBasicMaterial({ color: 0x00ff00 });
// const cube = new Three.Mesh(geometry, material);
// this.scene.add(cube)
dev update: it worked W
*/
}
render() {
if (!this.renderer) return;
if (!this.scene) return;
if (!this.camera) return;
if (this.needsToResizeCanvas()) {
this.lastStageSizeWhenRendering = {
width: this.runtime.stageWidth,
height: this.runtime.stageHeight
}
/*
multiply sizes because the stage looks like doo doo xd
we dont need to worry about multiplying 1920 * 2 since projects
shouldnt be using that large of a stage but instead a smaller size
with the same aspect ratio, penguinmod even says that
*/
this.renderer.setSize(this.lastStageSizeWhenRendering.width * 2, this.lastStageSizeWhenRendering.height * 2);
this.restyleExternalCanvas(this.renderer.domElement);
}
// when switching between project page & editor, we need to move the canvas again since it gets lost
/* todo: create layers so that iframe appears above 3d every time this is done */
this.appendElementAboveScratchCanvas(this.renderer.domElement);
this.updateScratchCanvasRelayering();
return new Promise((resolve) => {
// we do this to avoid HUGE lag when not waiting 1 tick
// and because it waits if the tab isnt focused
requestAnimationFrame(() => {
// renderer might not exist anymore
if (!this.renderer) return;
resolve(this.renderer.render(this.scene, this.camera));
})
})
}
setCameraPerspective2(args) {
if (this.camera) {
// remove existing camera
this.camera.remove();
this.camera = null;
}
const fov = Cast.toNumber(args.FOV);
const aspect = Cast.toNumber(args.AR);
const near = Cast.toNumber(args.NEAR);
const far = Cast.toNumber(args.FAR);
this.camera = new Three.PerspectiveCamera(fov, aspect, near, far);
}
setCameraPerspective1(args) {
/* todo: make near and far be the same as the existing camera if there is one */
const near = 0.1;
const far = 1000;
return this.setCameraPerspective2({
FOV: args.FOV,
AR: args.AR,
NEAR: near,
FAR: far
})
}
setCameraPerspective0(args) {
/* todo: make ar, near and far be the same as the existing camera if there is one */
const ar = this.runtime.stageWidth / this.runtime.stageHeight;
const near = 0.1;
const far = 1000;
return this.setCameraPerspective2({
FOV: args.FOV,
AR: ar,
NEAR: near,
FAR: far
})
}
setCameraPosition(args) {
if (!this.camera) return;
const position = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z),
}
this.camera.position.set(position.x, position.y, position.z);
}
setCameraRotation(args) {
if (!this.camera) return;
const rotation = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z),
}
// const euler = new Three.Euler(toRad(rotation.x), toRad(rotation.y), toRad(rotation.z));
// this.camera.setRotationFromEuler(euler);
const euler = new Three.Euler(0, 0, 0);
this.camera.setRotationFromEuler(euler);
this.camera.rotateY(toRad(rotation.y));
this.camera.rotateX(toRad(rotation.x));
this.camera.rotateZ(toRad(rotation.z));
}
getCameraPosition(args) {
if (!this.camera) return "";
const v = args.VECTOR3;
if (!v) return "";
if (!["x", "y", "z"].includes(v)) return "";
return Cast.toNumber(this.camera.position[v]);
}
getCameraRotation(args) {
if (!this.camera) return "";
const v = args.VECTOR3;
if (!v) return "";
if (!["x", "y", "z"].includes(v)) return "";
const rotation = Cast.toNumber(this.camera.rotation[v]);
// rotation is in radians, convert to degrees but round it
// a bit so that we get 46 instead of 45.999999999999996
return toDegRounding(rotation);
}
setSceneLayer(args) {
if (!this.renderer) return;
let lastSceneLayer = this.sceneLayer;
this.sceneLayer = "front";
if (Cast.toString(args.SIDE) === 'back') {
this.sceneLayer = "back";
}
if (this.sceneLayer !== lastSceneLayer) {
this.lastStageColor = this.runtime.renderer._backgroundColor4f;
}
this.appendElementAboveScratchCanvas(this.renderer.domElement);
this.updateScratchCanvasRelayering();
}
setSceneBackgroundColor(args) {
if (!this.renderer) return;
const rgb = Cast.toRgbColorObject(args.COLOR);
const color = Color.rgbToDecimal(rgb);
this.renderer.penguinMod.backgroundColor = color;
this.renderer.setClearColor(color, this.renderer.penguinMod.backgroundOpacity);
}
setSceneBackgroundOpacity(args) {
if (!this.renderer) return;
let opacity = Cast.toNumber(args.OPACITY);
if (opacity > 100) opacity = 100;
if (opacity < 0) opacity = 0;
const backgroundOpac = 1 - (opacity / 100);
this.renderer.penguinMod.backgroundOpacity = backgroundOpac;
this.renderer.setClearColor(this.renderer.penguinMod.backgroundColor, backgroundOpac);
}
show3d() {
this.renderer.domElement.style.display = ""
}
hide3d() {
this.renderer.domElement.style.display = "none"
}
is3dVisible() {
return this.renderer.domElement.style.display === "" || this.renderer.domElement.style.display === "absolute"
}
getCameraZoom() {
if (!this.camera) return "";
return Cast.toNumber(this.camera.zoom) * 100;
}
setCameraZoom(args) {
if (!this.camera) return;
this.camera.zoom = Cast.toNumber(args.ZOOM) / 100;
this.camera.updateProjectionMatrix();
}
getCameraClipPlane(args) {
if (!this.camera) return "";
const plane = args.CLIPPLANE;
if (!["near", "far"].includes(plane)) return "";
return this.camera[plane];
}
getCameraAspectRatio() {
if (!this.camera) return "";
return Cast.toNumber(this.camera.aspect);
}
getCameraFov() {
if (!this.camera) return "";
return Cast.toNumber(this.camera.fov);
}
isCameraPerspective() {
if (!this.camera) return false;
return Cast.toBoolean(this.camera.isPerspectiveCamera);
}
isCameraOrthographic() {
if (!this.camera) return false;
return Cast.toBoolean(!this.camera.isPerspectiveCamera);
}
doesObjectExist(args) {
if (!this.scene) return false;
const name = Cast.toString(args.NAME);
// !! is easier to type than if (...) { return true; } return false;
return !!this.scene.getObjectByName(name);
}
createGameObject(args, util, type) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
if (this.scene.getObjectByName(name)) return this.stackWarning(util, 'An object with this name already exists!');
const position = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z),
};
let object;
switch (type) {
case 'sphere': {
const geometry = new Three.SphereGeometry(1);
const material = new Three.MeshStandardMaterial({ color: 0xffffff });
const sphere = new Three.Mesh(geometry, material);
object = sphere;
break;
}
case 'plane': {
const geometry = new Three.PlaneGeometry(1, 1);
const material = new Three.MeshStandardMaterial({ color: 0xffffff });
const plane = new Three.Mesh(geometry, material);
object = plane;
break;
}
case 'mesh': {
const url = Cast.toString(args.URL);
// switch loaders based on file type
let fileType = 'obj';
switch (Cast.toString(args.FILETYPE)) {
case '.glb / .gltf':
fileType = 'glb';
break;
case '.fbx':
fileType = 'fbx';
break;
}
// we need to do a promise here so that stack continues on load
return new Promise((resolve) => {
let loader = MeshLoaders.OBJ;
switch (fileType) {
case 'glb':
loader = MeshLoaders.GLTF;
break;
case 'fbx':
loader = MeshLoaders.FBX;
break;
}
if (url in this.savedMeshes) {
const mesh = this.savedMeshes[url];
object = mesh.clone();
object.name = name;
this.existingSceneObjects.push(name);
object.isPenguinMod = true;
object.isMeshObj = true;
object.position.set(position.x, position.y, position.z);
this.scene.add(object);
resolve();
return;
}
else {
loader.load(url, (object) => {
// success
if (loader === MeshLoaders.GLTF) {
object = object.scene;
}
if (loader === MeshLoaders.OBJ) {
const material = new Three.MeshStandardMaterial({ color: 0xffffff });
material.wireframe = false;
this.updateMaterialOfObjObject(object, material);
this.savedMeshes[url] = object;
}
object.name = name;
console.log(object);
this.existingSceneObjects.push(name);
object.isPenguinMod = true;
object.isMeshObj = true;
object.position.set(position.x, position.y, position.z);
this.scene.add(object);
resolve();
}, () => { }, (error) => {
console.warn('Failed to load 3D mesh obj;', error);
this.stackWarning(util, 'Failed to get the 3D mesh!');
resolve();
})
}
});
}
case 'light': {
const type = Cast.toString(args.LIGHTTYPE);
// switch type because there are different types of lights
let light;
switch (type) {
default: {
light = new Three.PointLight(0xffffff, 1, 100);
break;
}
}
object = light;
this.existingSceneLights.push(name);
break;
}
default: {
const geometry = new Three.BoxGeometry(1, 1, 1);
const material = new Three.MeshStandardMaterial({ color: 0xffffff });
const cube = new Three.Mesh(geometry, material);
object = cube;
break;
}
}
object.name = name;
this.existingSceneObjects.push(name);
object.isPenguinMod = true;
object.position.set(position.x, position.y, position.z);
this.scene.add(object);
}
createCubeObject(args, util) {
this.createGameObject(args, util, 'cube');
}
createSphereObject(args, util) {
this.createGameObject(args, util, 'sphere');
}
createPlaneObject(args, util) {
this.createGameObject(args, util, 'plane');
}
createMeshObject(args, util) {
this.createGameObject(args, util, 'mesh');
}
createMeshObjectFileTyped(args, util) {
this.createGameObject(args, util, 'mesh');
}
createLightObject(args, util) {
this.createGameObject(args, util, 'light');
}
getMaterialOfObjObject(object) {
let material;
object.traverse((child) => {
if (child instanceof Three.Mesh) {
material = child.material;
}
});
return material;
}
updateMaterialOfObjObject(object, material) {
object.traverse((child) => {
if (child instanceof Three.Mesh) {
child.material = material;
}
});
}
setObjectPosition(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const position = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z),
};
const object = this.scene.getObjectByName(name);
if (!object) return;
object.position.set(position.x, position.y, position.z);
}
setObjectRotation(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const rotation = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z),
};
const object = this.scene.getObjectByName(name);
if (!object) return;
// const euler = new Three.Euler(toRad(rotation.x), toRad(rotation.y), toRad(rotation.z));
// object.setRotationFromEuler(euler);
const euler = new Three.Euler(0, 0, 0);
object.setRotationFromEuler(euler);
object.rotateY(toRad(rotation.y));
object.rotateX(toRad(rotation.x));
object.rotateZ(toRad(rotation.z));
}
setObjectSize(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const size = {
x: Cast.toNumber(args.X) / 100,
y: Cast.toNumber(args.Y) / 100,
z: Cast.toNumber(args.Z) / 100,
};
const object = this.scene.getObjectByName(name);
if (!object) return;
object.scale.set(size.x, size.y, size.z);
}
moveObjectUnits(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return;
const amount = Cast.toNumber(args.AMOUNT);
const direction = new Three.Vector3();
object.getWorldDirection(direction);
object.position.add(direction.multiplyScalar(amount));
}
getObjectPosition(args) {
if (!this.scene) return "";
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return '';
const v = args.VECTOR3;
if (!v) return "";
if (!["x", "y", "z"].includes(v)) return "";
return Cast.toNumber(object.position[v]);
}
getObjectRotation(args) {
if (!this.scene) return "";
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return '';
const v = args.VECTOR3;
if (!v) return "";
if (!["x", "y", "z"].includes(v)) return "";
const rotation = Cast.toNumber(object.rotation[v]);
// rotation is in radians, convert to degrees but round it
// a bit so that we get 46 instead of 45.999999999999996
return toDegRounding(rotation);
}
getObjectSize(args) {
if (!this.scene) return "";
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return '';
const v = args.VECTOR3;
if (!v) return "";
if (!["x", "y", "z"].includes(v)) return "";
return Cast.toNumber(object.scale[v]) * 100;
}
getObjectColor(args) {
if (!this.scene) return "";
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return '';
return "#" + object.material.color.getHexString()
}
deleteObject(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return;
const isLight = object.isLight;
object.clear();
this.scene.remove(object);
const idx = this.existingSceneObjects.indexOf(name);
this.existingSceneObjects.splice(idx, 1);
if (isLight) {
const lidx = this.existingSceneLights.indexOf(name);
this.existingSceneLights.splice(lidx, 1);
}
}
setObjectColor(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const rgb = Cast.toRgbColorObject(args.COLOR);
const color = Color.rgbToDecimal(rgb);
const object = this.scene.getObjectByName(name);
if (!object) return;
if (object.isLight) {
object.color.set(color);
return
}
if (object.isMeshObj) {
const material = this.getMaterialOfObjObject(object);
if (!material) return;
material.color.set(color);
this.updateMaterialOfObjObject(object, material);
return;
}
object.material.color.set(color);
}
setObjectShading(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const on = Cast.toString(args.ONOFF) === 'on';
const object = this.scene.getObjectByName(name);
if (!object) return;
if (object.isLight) return;
if (object.isMeshObj) {
const material = this.getMaterialOfObjObject(object);
if (!material) return;
const color = '#' + material.color.getHexString();
let newMat;
if (on) {
newMat = new Three.MeshStandardMaterial({ color: color });
} else {
newMat = new Three.MeshBasicMaterial({ color: color });
}
newMat.color.set(color);
this.updateMaterialOfObjObject(object, newMat);
return;
}
const color = '#' + object.material.color.getHexString();
if (on) {
object.material = new Three.MeshStandardMaterial({ color: color });
} else {
object.material = new Three.MeshBasicMaterial({ color: color });
}
}
setObjectWireframe(args) {
if (!this.scene) return;
const name = Cast.toString(args.NAME);
const on = Cast.toString(args.ONOFF) === 'on';
const object = this.scene.getObjectByName(name);
if (!object) return;
if (object.isLight) return;
if (object.isMeshObj) {
const material = this.getMaterialOfObjObject(object);
if (!material) return;
material.wireframe = on;
this.updateMaterialOfObjObject(object, material);
return;
}
object.material.wireframe = on;
}
existingObjectsArray(args) {
const listType = Cast.toString(args.OBJECTLIST);
const validOptions = ["objects", "physical objects", "lights"];
if (!validOptions.includes(listType)) return '[]';
switch (listType) {
case 'objects':
return JSON.stringify(this.existingSceneObjects);
case 'lights':
return JSON.stringify(this.existingSceneLights);
case 'physical objects': {
const physical = this.existingSceneObjects.filter(objectName => {
return !this.existingSceneLights.includes(objectName);
});
return JSON.stringify(physical);
}
default:
return '[]';
}
}
objectTouchingObject(args) {
if (!this.scene) return false;
const name1 = Cast.toString(args.NAME1);
const name2 = Cast.toString(args.NAME2);
const object1 = this.scene.getObjectByName(name1);
const object2 = this.scene.getObjectByName(name2);
if (!object1) return false;
if (!object2) return false;
if (object1.isLight) return false; // currently lights are not supported for collisions
if (object2.isLight) return false; // currently lights are not supported for collisions
const box1 = new Three.Box3().setFromObject(object1);
const box2 = new Three.Box3().setFromObject(object2);
const collision = box1.intersectsBox(box2);
return collision;
}
pointTowardsObject(args) {
if (!this.scene) return false;
const name1 = Cast.toString(args.NAME1);
const name2 = Cast.toString(args.NAME2);
const object1 = this.scene.getObjectByName(name1);
const object2 = this.scene.getObjectByName(name2);
if (!object1) return false;
if (!object2) return false;
object1.lookAt(object2.position);
}
pointTowardsXYZ(args) {
if (!this.scene) return false;
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return false;
const position = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z)
};
object.lookAt(position.x, position.y, position.z);
}
MoveCameraBy(args) {
if (!this.camera) return;
const amount = Cast.toNumber(args.AMOUNT);
// comment so it updates bc github was having problems
const direction = new Three.Vector3();
this.camera.getWorldDirection(direction);
this.camera.position.add(direction.multiplyScalar(amount));
}
changeCameraPosition(args) {
if (!this.camera) return;
this.camera.position.x += Cast.toNumber(args.X);
this.camera.position.y += Cast.toNumber(args.Y);
this.camera.position.z += Cast.toNumber(args.Z);
}
changeCameraRotation(args) {
if (!this.camera) return;
this.camera.rotation.x += Cast.toNumber(args.X);
this.camera.rotation.y += Cast.toNumber(args.Y);
this.camera.rotation.z += Cast.toNumber(args.Z);
}
raycastResultToReadable(result) {
const newResult = Clone.simple(result).map((intersection) => {
console.log(intersection.object.object.name);
return intersection.object.object.name;
})
return newResult;
}
rayCollision(args) {
if (!this.scene) return '';
const ray = new Three.Raycaster();
const origin = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z),
};
const direction = normalize({
x: toRad(Cast.toNumber(args.DX)),
y: toRad(Cast.toNumber(args.DY)),
z: toRad(Cast.toNumber(args.DZ)),
});
ray.set(new Three.Vector3(origin.x, origin.y, origin.z), new Three.Vector3(direction.x, direction.y, direction.z));
const intersects = ray.intersectObjects(this.scene.children, true);
if (intersects.length === 0) return '';
const first = intersects[0];
return first.object.name;
}
rayCollisionCamera() {
if (!this.scene) return '';
if (!this.camera) return '';
const ray = new Three.Raycaster();
ray.setFromCamera(new Three.Vector2(), this.camera);
const intersects = ray.intersectObjects(this.scene.children, true);
if (intersects.length === 0) return '';
const first = intersects[0];
return first.object.name;
}
rayCollisionArray(args) {
if (!this.scene) return '[]';
const ray = new Three.Raycaster();
const origin = {
x: Cast.toNumber(args.X),
y: Cast.toNumber(args.Y),
z: Cast.toNumber(args.Z),
};
const direction = normalize({
x: toRad(Cast.toNumber(args.DX)),
y: toRad(Cast.toNumber(args.DY)),
z: toRad(Cast.toNumber(args.DZ)),
});
ray.set(new Three.Vector3(origin.x, origin.y, origin.z), new Three.Vector3(direction.x, direction.y, direction.z));
const intersects = ray.intersectObjects(this.scene.children, true);
if (intersects.length === 0) return '[]';
//const result = this.raycastResultToReadable(intersects);
return JSON.stringify(intersects);
}
rayCollisionCameraArray() {
if (!this.scene) return '[]';
if (!this.camera) return '[]';
const ray = new Three.Raycaster();
ray.setFromCamera(new Three.Vector2(), this.camera);
const intersects = ray.intersectObjects(this.scene.children, true);
if (intersects.length === 0) return '[]';
//const result = this.raycastResultToReadable(intersects);
return JSON.stringify(intersects);
}
rayCollisionDistance(args) {
if (!this.scene) return '';
const origin = new Three.Vector3(
Cast.toNumber(args.X),
Cast.toNumber(args.Y),
Cast.toNumber(args.Z),
);
const direction = normalize(new Three.Vector3(
toRad(Cast.toNumber(args.DX)),
toRad(Cast.toNumber(args.DY)),
toRad(Cast.toNumber(args.DZ)),
));
const ray = new Three.Raycaster(origin, direction, 0, args.DIS);
const intersects = ray.intersectObjects(this.scene.children, true);
if (intersects.length === 0) return '';
const first = intersects[0];
return first.object.name;
}
rayCollisionArrayDistance(args) {
if (!this.scene) return '[]';
const origin = new Three.Vector3(
Cast.toNumber(args.X),
Cast.toNumber(args.Y),
Cast.toNumber(args.Z),
);
const direction = normalize(new Three.Vector3(
toRad(Cast.toNumber(args.DX)),
toRad(Cast.toNumber(args.DY)),
toRad(Cast.toNumber(args.DZ)),
));
const ray = new Three.Raycaster(origin, direction, 0, args.DIS);
const intersects = ray.intersectObjects(this.scene.children, true);
if (intersects.length === 0) return '[]';
const result = this.raycastResultToReadable(intersects);
return JSON.stringify(result);
}
getObjectParent(args) {
if (!this.scene) return '';
const name = Cast.toString(args.NAME);
const object = this.scene.getObjectByName(name);
if (!object) return '';
if (!object.parent) return '';
return object.parent.name;
}
}
module.exports = Jg3DBlocks;