Merge branch 'master' into feature/link-thumbnails

This commit is contained in:
Robert Long 2021-02-11 17:34:15 -08:00
Родитель 83b9cd3a7e 02c0a77ffd
Коммит ae7e808a28
8 изменённых файлов: 290 добавлений и 127 удалений

Просмотреть файл

@ -2,6 +2,6 @@ HUBS_SERVER="dev.reticulum.io"
RETICULUM_SERVER="dev.reticulum.io"
FARSPARK_SERVER="farspark-dev.reticulum.io"
NON_CORS_PROXY_DOMAINS="hubs.local,localhost"
CORS_PROXY_SERVER="hubs-proxy.com"
CORS_PROXY_SERVER="cors-proxy-dev.reticulum.io"
HOST_PORT="9090"
IS_MOZ="false"

Просмотреть файл

@ -891,7 +891,7 @@ export default class Project extends EventEmitter {
allow_promotion: publishParams.allowPromotion,
name: publishParams.name,
attributions: {
creator: publishParams.creatorAttribution,
creator: publishParams.creatorAttribution && publishParams.creatorAttribution.trim(),
content: publishParams.contentAttributions
}
};

Просмотреть файл

@ -8,12 +8,18 @@ import mergeMeshGeometries from "../utils/mergeMeshGeometries";
import RecastClient from "../recast/RecastClient";
import HeightfieldClient from "../heightfield/HeightfieldClient";
import SpawnPointNode from "../nodes/SpawnPointNode";
import { RethrownError } from "../utils/errors";
import * as recastWasmUrl from "recast-wasm/dist/recast.wasm";
import traverseFilteredSubtrees from "../utils/traverseFilteredSubtrees";
const recastClient = new RecastClient();
const heightfieldClient = new HeightfieldClient();
export const NavMeshMode = {
Automatic: "automatic",
Custom: "custom"
};
export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
static nodeName = "Floor Plan";
@ -25,7 +31,7 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
return editor.scene.findNodeByType(FloorPlanNode) === null;
}
static async deserialize(editor, json) {
static async deserialize(editor, json, loadAsync, onError) {
const node = await super.deserialize(editor, json);
const {
@ -38,7 +44,9 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
agentMaxSlope,
regionMinSize,
maxTriangles,
forceTrimesh
forceTrimesh,
navMeshMode,
navMeshSrc
} = json.components.find(c => c.name === "floor-plan").props;
node.autoCellSize = autoCellSize;
@ -52,6 +60,14 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
node.maxTriangles = maxTriangles || 1000;
node.forceTrimesh = forceTrimesh || false;
node._navMeshMode = navMeshMode || NavMeshMode.Automatic;
if (navMeshMode === NavMeshMode.Custom) {
loadAsync(node.load(navMeshSrc, onError));
} else {
node._navMeshSrc = navMeshSrc || "";
}
return node;
}
@ -68,6 +84,88 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
this.maxTriangles = 1000;
this.forceTrimesh = false;
this.heightfieldMesh = null;
this._navMeshMode = NavMeshMode.Automatic;
this._navMeshSrc = "";
this.navMesh = null;
this.trimesh = null;
this.heightfieldMesh = null;
}
get navMeshMode() {
return this._navMeshMode;
}
set navMeshMode(value) {
if (value === NavMeshMode.Custom) {
// Force reloading nav mesh since it was removed and this._navMeshSrc didn't change
this.load(this._navMeshSrc, undefined, true).catch(console.error);
} else if (this.navMesh) {
this.remove(this.navMesh);
this.navMesh = null;
}
this._navMeshMode = value;
}
get navMeshSrc() {
return this._navMeshSrc;
}
set navMeshSrc(value) {
this.load(value).catch(console.error);
}
async load(src, onError, force = false) {
const nextSrc = src || "";
if (nextSrc === this._navMeshSrc && nextSrc !== "" && !force) {
return;
}
this._navMeshSrc = nextSrc;
this.issues = [];
if (this.navMesh) {
this.remove(this.navMesh);
this.navMesh = null;
}
try {
const { accessibleUrl } = await this.editor.api.resolveMedia(nextSrc);
const loader = this.editor.gltfCache.getLoader(accessibleUrl);
const { scene } = await loader.getDependency("gltf");
const mesh = scene.getObjectByProperty("type", "Mesh");
if (!mesh) {
throw new Error("No mesh available.");
}
const geometry = mesh.geometry.clone(); // Clone in case the user reuses a mesh for the navmesh.
mesh.updateMatrixWorld();
geometry.applyMatrix(mesh.matrixWorld);
this.setNavMesh(new Mesh(geometry, new MeshBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.2 })));
if (this.navMesh) {
this.navMesh.visible = this.editor.selected.indexOf(this) !== -1;
}
} catch (error) {
const modelError = new RethrownError(`Error loading custom navmesh "${this._navMeshSrc}"`, error);
if (onError) {
onError(this, modelError);
}
console.error(modelError);
this.issues.push({ severity: "error", message: "Error loading custom navmesh." });
}
this.editor.emit("objectsChanged", [this]);
this.editor.emit("selectionChanged");
}
onSelect() {
@ -99,7 +197,6 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
}
async generate(signal) {
window.scene = this;
const collidableMeshes = [];
const walkableMeshes = [];
@ -129,56 +226,63 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
}
});
const boxColliderNodes = this.editor.scene.getNodesByType(BoxColliderNode, false);
if (this.navMeshMode === NavMeshMode.Automatic) {
const boxColliderNodes = this.editor.scene.getNodesByType(BoxColliderNode, false);
for (const node of boxColliderNodes) {
if (node.walkable) {
const helperMesh = node.helper.object;
const boxColliderMesh = new Mesh(helperMesh.geometry, new MeshBasicMaterial());
boxColliderMesh.applyMatrix(node.matrixWorld);
boxColliderMesh.updateMatrixWorld();
walkableMeshes.push(boxColliderMesh);
for (const node of boxColliderNodes) {
if (node.walkable) {
const helperMesh = node.helper.object;
const boxColliderMesh = new Mesh(helperMesh.geometry, new MeshBasicMaterial());
boxColliderMesh.applyMatrix(node.matrixWorld);
boxColliderMesh.updateMatrixWorld();
walkableMeshes.push(boxColliderMesh);
}
}
}
const walkableGeometry = mergeMeshGeometries(walkableMeshes);
const walkableGeometry = mergeMeshGeometries(walkableMeshes);
const box = new Box3().setFromBufferAttribute(walkableGeometry.attributes.position);
const size = new Vector3();
box.getSize(size);
if (Math.max(size.x, size.y, size.z) > 2000) {
throw new Error(
`Scene is too large (${size.x.toFixed(3)} x ${size.y.toFixed(3)} x ${size.z.toFixed(3)}) ` +
`to generate a floor plan.\n` +
`You can un-check the "walkable" checkbox on models to exclude them from the floor plan.`
const box = new Box3().setFromBufferAttribute(walkableGeometry.attributes.position);
const size = new Vector3();
box.getSize(size);
if (Math.max(size.x, size.y, size.z) > 2000) {
throw new Error(
`Scene is too large (${size.x.toFixed(3)} x ${size.y.toFixed(3)} x ${size.z.toFixed(3)}) ` +
`to generate a floor plan.\n` +
`You can un-check the "walkable" checkbox on models to exclude them from the floor plan.`
);
}
const area = size.x * size.z;
// Tuned to produce cell sizes from ~0.5 to ~1.5 for areas from ~200 to ~350,000.
const cellSize = this.autoCellSize ? Math.pow(area, 1 / 3) / 50 : this.cellSize;
const navGeometry = await recastClient.buildNavMesh(
walkableGeometry,
{
cellSize,
cellHeight: this.cellHeight,
agentHeight: this.agentHeight,
agentRadius: this.agentRadius,
agentMaxClimb: this.agentMaxClimb,
agentMaxSlope: this.agentMaxSlope,
regionMinSize: this.regionMinSize,
wasmUrl: new URL(recastWasmUrl, configs.BASE_ASSETS_PATH || window.location).href
},
signal
);
const navMesh = new Mesh(
navGeometry,
new MeshBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.2 })
);
this.setNavMesh(navMesh);
}
const area = size.x * size.z;
// Tuned to produce cell sizes from ~0.5 to ~1.5 for areas from ~200 to ~350,000.
const cellSize = this.autoCellSize ? Math.pow(area, 1 / 3) / 50 : this.cellSize;
const navGeometry = await recastClient.buildNavMesh(
walkableGeometry,
{
cellSize,
cellHeight: this.cellHeight,
agentHeight: this.agentHeight,
agentRadius: this.agentRadius,
agentMaxClimb: this.agentMaxClimb,
agentMaxSlope: this.agentMaxSlope,
regionMinSize: this.regionMinSize,
wasmUrl: new URL(recastWasmUrl, configs.BASE_ASSETS_PATH || window.location).href
},
signal
);
const navMesh = new Mesh(navGeometry, new MeshBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.2 }));
this.setNavMesh(navMesh);
navMesh.visible = this.editor.selected.indexOf(this) !== -1;
if (this.navMesh) {
this.navMesh.visible = this.editor.selected.indexOf(this) !== -1;
}
const collidableGeometry = mergeMeshGeometries(collidableMeshes);
@ -252,6 +356,7 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
copy(source, recursive = true) {
if (recursive) {
this.remove(this.heightfieldMesh);
this.remove(this.navMesh);
}
super.copy(source, recursive);
@ -262,6 +367,12 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
if (heightfieldMeshIndex !== -1) {
this.heightfieldMesh = this.children[heightfieldMeshIndex];
}
const navMeshIndex = source.children.findIndex(child => child === source.navMesh);
if (navMeshIndex !== -1) {
this.navMesh = this.children[navMeshIndex];
}
}
this.autoCellSize = source.autoCellSize;
@ -274,6 +385,9 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
this.regionMinSize = source.regionMinSize;
this.maxTriangles = source.maxTriangles;
this.forceTrimesh = source.forceTrimesh;
this._navMeshMode = source._navMeshMode;
this._navMeshSrc = source._navMeshSrc;
return this;
}
@ -289,7 +403,9 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
agentMaxSlope: this.agentMaxSlope,
regionMinSize: this.regionMinSize,
maxTriangles: this.maxTriangles,
forceTrimesh: this.forceTrimesh
forceTrimesh: this.forceTrimesh,
navMeshMode: this.navMeshMode,
navMeshSrc: this.navMeshSrc
}
});
}
@ -301,9 +417,14 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
this.remove(this.heightfieldMesh);
}
const navMeshMaterial = this.navMesh.material;
if (!this.navMesh && this.navMeshMode === NavMeshMode.Custom) {
throw new Error("The FloorPlan Node is set to use a custom navigation mesh but none was provided.");
}
const navMeshMaterial = this.navMesh.material.clone();
navMeshMaterial.transparent = true;
navMeshMaterial.opacity = 0;
this.navMesh.material = navMeshMaterial;
this.navMesh.name = "navMesh";
this.navMesh.userData.gltfExtensions = {
@ -315,10 +436,11 @@ export default class FloorPlanNode extends EditorNodeMixin(FloorPlan) {
if (this.trimesh) {
this.trimesh.name = "trimesh";
const trimeshMaterial = this.trimesh.material;
const trimeshMaterial = this.trimesh.material.clone();
trimeshMaterial.transparent = true;
trimeshMaterial.opacity = 0;
trimeshMaterial.wireframe = false;
this.trimesh.material = trimeshMaterial;
this.trimesh.userData.gltfExtensions = {
MOZ_hubs_components: {

Просмотреть файл

@ -656,7 +656,13 @@ export default class SceneNode extends EditorNodeMixin(Scene) {
if (!attribution) return;
const attributionKey = attribution.url || `${attribution.title}_${attribution.author}`;
const url = attribution.url && attribution.url.trim();
const title = attribution.title && attribution.title.trim();
const author = attribution.author && attribution.author.trim();
if (!url && !title && !author) return;
const attributionKey = url || `${title}_${author}`;
if (seenAttributions.has(attributionKey)) return;
seenAttributions.add(attributionKey);
contentAttributions.push(attribution);

Просмотреть файл

@ -10,6 +10,20 @@ import ErrorDialog from "../dialogs/ErrorDialog";
import { withDialog } from "../contexts/DialogContext";
import { withSettings } from "../contexts/SettingsContext";
import { ShoePrints } from "styled-icons/fa-solid/ShoePrints";
import { NavMeshMode } from "../../editor/nodes/FloorPlanNode";
import SelectInput from "../inputs/SelectInput";
import ModelInput from "../inputs/ModelInput";
const NavMeshModeOptions = [
{
label: "Automatic",
value: NavMeshMode.Automatic
},
{
label: "Custom",
value: NavMeshMode.Custom
}
];
class FloorPlanNodeEditor extends Component {
static propTypes = {
@ -37,6 +51,8 @@ class FloorPlanNodeEditor extends Component {
this.onChangeRegionMinSize = createPropSetter("regionMinSize");
this.onChangeMaxTriangles = createPropSetter("maxTriangles");
this.onChangeForceTrimesh = createPropSetter("forceTrimesh");
this.onChangeNavMeshMode = createPropSetter("navMeshMode");
this.onChangeNavMeshSrc = createPropSetter("navMeshSrc");
}
onRegenerate = async () => {
@ -72,82 +88,93 @@ class FloorPlanNodeEditor extends Component {
return (
<NodeEditor {...this.props} description={FloorPlanNodeEditor.description}>
<InputGroup name="Auto Cell Size">
<BooleanInput value={node.autoCellSize} onChange={this.onChangeAutoCellSize} />
<InputGroup name="Nav Mesh Mode">
<SelectInput options={NavMeshModeOptions} value={node.navMeshMode} onChange={this.onChangeNavMeshMode} />
</InputGroup>
{!node.autoCellSize && (
<NumericInputGroup
name="Cell Size"
value={node.cellSize}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
min={0.1}
displayPrecision={0.0001}
onChange={this.onChangeCellSize}
/>
{node.navMeshMode === NavMeshMode.Automatic ? (
<>
<InputGroup name="Auto Cell Size">
<BooleanInput value={node.autoCellSize} onChange={this.onChangeAutoCellSize} />
</InputGroup>
{!node.autoCellSize && (
<NumericInputGroup
name="Cell Size"
value={node.cellSize}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
min={0.1}
displayPrecision={0.0001}
onChange={this.onChangeCellSize}
/>
)}
<NumericInputGroup
name="Cell Height"
value={node.cellHeight}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
min={0.1}
onChange={this.onChangeCellHeight}
unit="m"
/>
<NumericInputGroup
name="Agent Height"
value={node.agentHeight}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
min={0.1}
onChange={this.onChangeAgentHeight}
unit="m"
/>
<NumericInputGroup
name="Agent Radius"
value={node.agentRadius}
min={0}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
onChange={this.onChangeAgentRadius}
unit="m"
/>
<NumericInputGroup
name="Maximum Step Height"
value={node.agentMaxClimb}
min={0}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
onChange={this.onChangeAgentMaxClimb}
unit="m"
/>
<NumericInputGroup
name="Maximum Slope"
value={node.agentMaxSlope}
min={0.00001}
max={90}
smallStep={1}
mediumStep={5}
largeStep={15}
onChange={this.onChangeAgentMaxSlope}
unit="°"
/>
<NumericInputGroup
name="Minimum Region Area"
value={node.regionMinSize}
min={0.1}
smallStep={0.1}
mediumStep={1}
largeStep={10}
onChange={this.onChangeRegionMinSize}
unit="m²"
/>
</>
) : (
<InputGroup name="Custom Navmesh Url">
<ModelInput value={node.navMeshSrc} onChange={this.onChangeNavMeshSrc} />
</InputGroup>
)}
<NumericInputGroup
name="Cell Height"
value={node.cellHeight}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
min={0.1}
onChange={this.onChangeCellHeight}
unit="m"
/>
<NumericInputGroup
name="Agent Height"
value={node.agentHeight}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
min={0.1}
onChange={this.onChangeAgentHeight}
unit="m"
/>
<NumericInputGroup
name="Agent Radius"
value={node.agentRadius}
min={0}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
onChange={this.onChangeAgentRadius}
unit="m"
/>
<NumericInputGroup
name="Maximum Step Height"
value={node.agentMaxClimb}
min={0}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
onChange={this.onChangeAgentMaxClimb}
unit="m"
/>
<NumericInputGroup
name="Maximum Slope"
value={node.agentMaxSlope}
min={0.00001}
max={90}
smallStep={1}
mediumStep={5}
largeStep={15}
onChange={this.onChangeAgentMaxSlope}
unit="°"
/>
<NumericInputGroup
name="Minimum Region Area"
value={node.regionMinSize}
min={0.1}
smallStep={0.1}
mediumStep={1}
largeStep={10}
onChange={this.onChangeRegionMinSize}
unit="m²"
/>
<InputGroup name="Force Trimesh">
<BooleanInput value={node.forceTrimesh} onChange={this.onChangeForceTrimesh} />
</InputGroup>

Просмотреть файл

@ -58,7 +58,7 @@ export default class SpotLightNodeEditor extends Component {
largeStep={0.1}
value={node.intensity}
onChange={this.onChangeIntensity}
unit="°"
unit="cd"
/>
<RadianNumericInputGroup
name="Inner Cone Angle"

Просмотреть файл

@ -732,6 +732,8 @@ Generated by [AVA](https://avajs.dev).
cellSize: 1,
forceTrimesh: false,
maxTriangles: 1000,
navMeshMode: 'automatic',
navMeshSrc: '',
regionMinSize: 4,
},
},
@ -1420,6 +1422,8 @@ Generated by [AVA](https://avajs.dev).
cellSize: 0.1200000000000001,
forceTrimesh: false,
maxTriangles: 1000,
navMeshMode: 'automatic',
navMeshSrc: '',
regionMinSize: 4,
},
},
@ -2730,6 +2734,8 @@ Generated by [AVA](https://avajs.dev).
cellSize: 0.1200000000000001,
forceTrimesh: false,
maxTriangles: 1000,
navMeshMode: 'automatic',
navMeshSrc: '',
regionMinSize: 4,
},
},
@ -4115,6 +4121,8 @@ Generated by [AVA](https://avajs.dev).
cellSize: 0.1200000000000001,
forceTrimesh: false,
maxTriangles: 1000,
navMeshMode: 'automatic',
navMeshSrc: '',
regionMinSize: 4,
},
},

Двоичные данные
test/integration/snapshots/Editor.test.js.snap

Двоичный файл не отображается.