Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions src/controls/Transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ export default class TransformControls extends Object3D {
this.gizmo = new Gizmo();
this.plane = new Plane();

// Set gizmo and plane to layer 1 ONLY so mirrors don't render them
// Main camera must enable layer 1 to see these
this.gizmo.layers.set(1);
this.plane.layers.set(1);
// Set gizmo and plane to layer 2 ONLY so they render in a dedicated pass
// on top of everything (sky, post-processing, etc.)
// Layer 1 = editor helpers (togglable), Layer 2 = gizmo (always visible)
this.gizmo.layers.set(2);
this.plane.layers.set(2);

// Helper function to set depth properties on materials
const setMaterialDepth = material => {
Expand All @@ -46,15 +47,15 @@ export default class TransformControls extends Object3D {
});
};

// Also set layer 1 on all children recursively
// Set high renderOrder and disable depthTest so gizmos render on top of sky/water
// Also set layer 2 on all children recursively
// Disable depthTest so gizmos render on top of everything
this.gizmo.traverse(child => {
child.layers.set(1);
child.layers.set(2);
child.renderOrder = 999;
setMaterialDepth(child.material);
});
this.plane.traverse(child => {
child.layers.set(1);
child.layers.set(2);
child.renderOrder = 999;
setMaterialDepth(child.material);
});
Expand Down Expand Up @@ -97,8 +98,8 @@ export default class TransformControls extends Object3D {
this.setAndDispatch("showZ", true);

this.ray = new Raycaster();
// Enable layer 1 so raycaster can pick gizmo objects (which are on layer 1)
this.ray.layers.enable(1);
// Enable layer 2 so raycaster can pick gizmo objects (which are on layer 2)
this.ray.layers.enable(2);

this._tempVector = new Vector3();
this._tempVector2 = new Vector3();
Expand Down
5 changes: 5 additions & 0 deletions src/core/Level.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export class Level extends EventDispatcher {
Scene.render(dt);
}

// Render gizmo layer on top of everything (separate pass with depth clear)
// This ensures gizmos are always visible regardless of sky, post-processing,
// or the sortObjects=false setting used when shadows are enabled.
Scene.renderGizmoLayer();

Particles.update(dt);
this.onUpdate(dt);
Scene.update(dt);
Expand Down
48 changes: 47 additions & 1 deletion src/core/Scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,12 @@ export class Scene {

createCamera(camera) {
this.camera = camera;
// Enable layer 1 so camera can see editor-only objects (helpers, grid, gizmos)
// Enable layer 1 so camera can see editor-only objects (helpers, grid)
// Enable layer 2 so camera can see gizmos (rendered in separate pass)
// Mirror cameras only use layer 0, so they won't render these
if (this.camera && this.camera.getBody()) {
this.camera.getBody().layers.enable(1);
this.camera.getBody().layers.enable(2);
}
}

Expand Down Expand Up @@ -305,6 +307,50 @@ export class Scene {
this.renderer.render(this.scene, this.camera.getBody());
}

// Render gizmo layer (layer 2) on top of everything.
// Called after main render or post-processing to ensure gizmos are always visible
// regardless of sky, post-processing, or sortObjects setting.
renderGizmoLayer() {
if (!this.renderer || !this.camera) return;

const cameraBody = this.camera.getBody();

// Save current camera layer mask
const savedLayers = cameraBody.layers.mask;

// Set camera to only see layer 2 (gizmo layer)
cameraBody.layers.set(2);

// Ensure we render to screen (not a post-processing buffer)
this.renderer.setRenderTarget(null);
// Clear only depth so gizmo renders on top of the already-drawn frame
this.renderer.autoClear = false;
this.renderer.clearDepth();
this.renderer.render(this.scene, cameraBody);
this.renderer.autoClear = true;

// Restore camera layers
cameraBody.layers.mask = savedLayers;
}

// Toggle editor helpers visibility (layer 1: grid, light helpers, camera helpers)
// Does NOT affect gizmos (layer 2) which are always visible
setHelpersVisible(visible) {
if (!this.camera || !this.camera.getBody()) return;

const cameraBody = this.camera.getBody();
if (visible) {
cameraBody.layers.enable(1);
} else {
cameraBody.layers.disable(1);
}
this.helpersVisible = visible;
}

getHelpersVisible() {
return this.helpersVisible !== false;
}

setFog(color, density) {
this.scene.fog = new FogExp2(color, density);
Config.setConfig({
Expand Down
23 changes: 23 additions & 0 deletions src/entities/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
extractBiggestBoundingBox,
extractBoundingSphere,
extractBiggestBoundingSphere,
parseBoundingBoxSize,
} from "../physics/utils";

import { clamp } from "../lib/math";
Expand Down Expand Up @@ -135,6 +136,27 @@ export default class Element extends Entity {
}
}

getComputedColliderSize() {
const result = {};

try {
if (this.boundingBox) {
const size = parseBoundingBoxSize(this.boundingBox);
const scale = this.getScale();
result.width = parseFloat((size.x * scale.x).toFixed(3));
result.height = parseFloat((size.y * scale.y).toFixed(3));
result.length = parseFloat((size.z * scale.z).toFixed(3));
}
if (this.boundingSphere) {
result.radius = parseFloat(this.boundingSphere.radius.toFixed(3));
}
} catch {
// bounding box/sphere not yet available
}

return result;
}

addToScene() {
const { addUniverse = true } = this.options;

Expand Down Expand Up @@ -1160,6 +1182,7 @@ export default class Element extends Entity {
// Physics options (state is not used by Importer, only options)
physics: {
options: this.getPhysicsOptions(),
computedSize: this.getComputedColliderSize(),
},
// Textures with serialized map
textures: serializeMap(this.textures),
Expand Down
6 changes: 6 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import Sound from "./audio/Sound";

import * as THREE from "three";
import { Vector3, EventDispatcher } from "three";
import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry.js";
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
import Level, { author } from "./core/Level";

import Universe from "./core/universe";
Expand Down Expand Up @@ -221,6 +224,9 @@ export {
easing,
Stats,
THREE,
LineSegments2,
LineSegmentsGeometry,
LineMaterial,
rxjs,
xstate,
map,
Expand Down
38 changes: 20 additions & 18 deletions src/physics/hitbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,33 @@ const DEFAULT_HITBOX_OPTIONS = {
};

export const getBoxHitbox = element => {
const size = new Vector3();
element.boundingBox.getSize(size);

const scaledSize = {
x: size.x + HIT_BOX_INCREASE,
y: size.y + HIT_BOX_INCREASE,
z: size.z + HIT_BOX_INCREASE,
};
const box = new Box(
scaledSize.x,
scaledSize.y,
scaledSize.z,
HIT_BOX_COLOR,
DEFAULT_HITBOX_OPTIONS,
);

//box.setQuaternion(quaternion);
const opts = element.getPhysicsOptions() || {};
let w, h, l;

if (opts.colliderWidth != null || opts.colliderHeight != null || opts.colliderLength != null) {
w = (opts.colliderWidth ?? 1) + HIT_BOX_INCREASE;
h = (opts.colliderHeight ?? 1) + HIT_BOX_INCREASE;
l = (opts.colliderLength ?? 1) + HIT_BOX_INCREASE;
} else {
const size = new Vector3();
element.boundingBox.getSize(size);
w = size.x + HIT_BOX_INCREASE;
h = size.y + HIT_BOX_INCREASE;
l = size.z + HIT_BOX_INCREASE;
}

const box = new Box(w, h, l, HIT_BOX_COLOR, DEFAULT_HITBOX_OPTIONS);

box.setWireframe(true);
box.setWireframeLineWidth(2);

return box;
};

export const getSphereHitbox = element => {
const radius = element.boundingSphere.radius;
const opts = element.getPhysicsOptions() || {};
const radius =
opts.colliderRadius != null ? opts.colliderRadius : element.boundingSphere.radius;
const sphere = new Sphere(radius, HIT_BOX_COLOR, DEFAULT_HITBOX_OPTIONS);

sphere.setWireframe(true);
Expand Down
16 changes: 14 additions & 2 deletions src/physics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,26 @@ export class Physics extends EventDispatcher {

add(element, options = {}) {
if (Config.physics().enabled) {
const { colliderType = COLLIDER_TYPES.BOX } = options;
const {
colliderType = COLLIDER_TYPES.BOX,
colliderWidth,
colliderHeight,
colliderLength,
colliderRadius,
...rest
} = options;

const uuid = element.uuid();
const description = {
...mapColliderTypeToDescription(colliderType)(element),
...options,
...rest,
};

if (colliderWidth != null) description.width = colliderWidth;
if (colliderHeight != null) description.height = colliderHeight;
if (colliderLength != null) description.length = colliderLength;
if (colliderRadius != null) description.radius = colliderRadius;

this.storeElement(element, options);

this.worker.postMessage({
Expand Down
Loading