153 lines
4.2 KiB
JavaScript
153 lines
4.2 KiB
JavaScript
import * as THREE from "three";
|
|
import * as CANNON from "cannon-es";
|
|
import { Instance } from "./Instance.js";
|
|
|
|
export class BasePart extends Instance {
|
|
constructor() {
|
|
super("BasePart");
|
|
|
|
// === Visual properties ===
|
|
this.Color = 0x00ff00;
|
|
this.Size = new THREE.Vector3(1, 1, 1);
|
|
this.Position = new THREE.Vector3(0, 0, 0);
|
|
this.Orientation = new THREE.Euler(0, 0, 0);
|
|
this.Anchored = false; // Roblox-style toggle
|
|
|
|
// === Three.js setup ===
|
|
const geometry = new THREE.BoxGeometry(this.Size.x, this.Size.y, this.Size.z);
|
|
const material = new THREE.MeshStandardMaterial({ color: this.Color });
|
|
this.mesh = new THREE.Mesh(geometry, material);
|
|
this.mesh.castShadow = true;
|
|
this.mesh.receiveShadow = true;
|
|
this.mesh.userData.basePart = this;
|
|
|
|
// === Cannon.js setup ===
|
|
const shape = new CANNON.Box(
|
|
new CANNON.Vec3(this.Size.x / 2, this.Size.y / 2, this.Size.z / 2)
|
|
);
|
|
this.body = new CANNON.Body({
|
|
mass: 1,
|
|
position: new CANNON.Vec3(this.Position.x, this.Position.y, this.Position.z),
|
|
shape: shape,
|
|
});
|
|
this.body.userData = { basePart: this };
|
|
|
|
this._originalMass = this.body.mass; // store default mass
|
|
this._renderService = null; // assigned when added to scene
|
|
}
|
|
|
|
// === Scene lifecycle ===
|
|
OnAddedToScene(parent) {
|
|
const dm = this.GetDataModel();
|
|
const renderService = dm.GetService("RenderService");
|
|
this._renderService = renderService;
|
|
|
|
if (renderService.scene) renderService.scene.add(this.mesh);
|
|
if (!renderService.physicsWorld) renderService._initPhysicsWorld();
|
|
|
|
renderService.physicsWorld.addBody(this.body);
|
|
this._applyAnchoredState();
|
|
}
|
|
|
|
// === Anchoring logic ===
|
|
setAnchored(value) {
|
|
if (this.Anchored === value) return;
|
|
this.Anchored = value;
|
|
this._applyAnchoredState();
|
|
}
|
|
|
|
_applyAnchoredState() {
|
|
if (!this.body) return;
|
|
if (this.Anchored) {
|
|
this.body.type = CANNON.Body.STATIC;
|
|
this.body.mass = 0;
|
|
this.body.velocity.set(0, 0, 0);
|
|
this.body.angularVelocity.set(0, 0, 0);
|
|
} else {
|
|
this.body.type = CANNON.Body.DYNAMIC;
|
|
this.body.mass = this._originalMass || 1;
|
|
}
|
|
this.body.updateMassProperties();
|
|
}
|
|
|
|
// === Updates ===
|
|
updateVisual() {
|
|
// Only update mesh from physics if not anchored
|
|
if (!this.Anchored) {
|
|
this.mesh.position.copy(this.body.position);
|
|
this.mesh.quaternion.copy(this.body.quaternion);
|
|
}
|
|
}
|
|
|
|
updateBodyPosition() {
|
|
this.body.position.set(this.Position.x, this.Position.y, this.Position.z);
|
|
this.body.quaternion.setFromEuler(
|
|
this.Orientation.x,
|
|
this.Orientation.y,
|
|
this.Orientation.z
|
|
);
|
|
this.body.velocity.set(0, 0, 0);
|
|
this.body.angularVelocity.set(0, 0, 0);
|
|
}
|
|
|
|
updateSizes() {
|
|
// Update Three geometry
|
|
this.mesh.geometry.dispose();
|
|
this.mesh.geometry = new THREE.BoxGeometry(this.Size.x, this.Size.y, this.Size.z);
|
|
|
|
// Update Cannon shape
|
|
this.body.shapes = [];
|
|
const newShape = new CANNON.Box(
|
|
new CANNON.Vec3(this.Size.x / 2, this.Size.y / 2, this.Size.z / 2)
|
|
);
|
|
this.body.addShape(newShape);
|
|
this.body.updateMassProperties();
|
|
}
|
|
|
|
OnReplicated() {
|
|
if (this.IsReplicated) {
|
|
this.body.type = CANNON.Body.KINEMATIC;
|
|
this.body.velocity.set(0, 0, 0);
|
|
this.body.angularVelocity.set(0, 0, 0);
|
|
}
|
|
|
|
this.body.position.copy(this.Position);
|
|
this.body.quaternion.setFromEuler(
|
|
this.Orientation.x,
|
|
this.Orientation.y,
|
|
this.Orientation.z
|
|
);
|
|
|
|
this.updateVisual();
|
|
}
|
|
|
|
OnPhysicsTick() {
|
|
|
|
if (this.body.position.y < -1000) {
|
|
// Fell out of world, destroy like in Roblox
|
|
this.Destroy();
|
|
console.debug("BasePart fell out of world and was destroyed.");
|
|
return;
|
|
}
|
|
|
|
if (this.IsReplicated || this.Anchored) return;
|
|
|
|
this.Position.set(
|
|
this.body.position.x,
|
|
this.body.position.y,
|
|
this.body.position.z
|
|
);
|
|
this.Orientation.setFromQuaternion(this.body.quaternion);
|
|
}
|
|
|
|
// === Cleanup ===
|
|
Destroy() {
|
|
if (this._renderService) {
|
|
if (this.mesh.parent) this.mesh.parent.remove(this.mesh);
|
|
if (this._renderService.physicsWorld)
|
|
this._renderService.physicsWorld.removeBody(this.body);
|
|
}
|
|
super.Destroy();
|
|
}
|
|
}
|