Feat/map-environment #6
+40717
-39869
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+213
-62
@@ -13,7 +13,11 @@ const MESH_NAME_MAPPINGS = {
|
|||||||
panneauxquartier: "panneauaffichage",
|
panneauxquartier: "panneauaffichage",
|
||||||
};
|
};
|
||||||
|
|
||||||
const REMOVED_NODE_NAMES = new Set(["ROOT", "mc"]);
|
const IDENTITY_NODE = {
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
scale: [1, 1, 1],
|
||||||
|
};
|
||||||
|
|
||||||
function cloneNode(node) {
|
function cloneNode(node) {
|
||||||
return {
|
return {
|
||||||
@@ -25,32 +29,147 @@ function cloneNode(node) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapMeshName(node) {
|
function createGroup(name, sourceNode) {
|
||||||
if (node.type !== "Mesh") {
|
return {
|
||||||
return cloneNode(node);
|
name,
|
||||||
}
|
type: "Object3D",
|
||||||
|
role: "group",
|
||||||
|
position: sourceNode?.position ?? IDENTITY_NODE.position,
|
||||||
|
rotation: sourceNode?.rotation ?? IDENTITY_NODE.rotation,
|
||||||
|
scale: sourceNode?.scale ?? IDENTITY_NODE.scale,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapMeshNode(node) {
|
||||||
return {
|
return {
|
||||||
...cloneNode(node),
|
...cloneNode(node),
|
||||||
name: MESH_NAME_MAPPINGS[node.name] ?? node.name,
|
name: MESH_NAME_MAPPINGS[node.name] ?? node.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGroup(node, children = []) {
|
function getOrCreateModelGroup(parent, modelName) {
|
||||||
|
let group = parent.children.find(
|
||||||
|
(child) => child.name === modelName && child.type === "Object3D",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
group = createGroup(modelName);
|
||||||
|
parent.children.push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRenderableObject(objectNode, meshNode) {
|
||||||
|
const mappedMesh = mapMeshNode(meshNode);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...cloneNode(node),
|
...cloneNode(objectNode ?? meshNode),
|
||||||
name: node.name === "Neutre" ? "blocking" : node.name,
|
name: mappedMesh.name,
|
||||||
children,
|
type: "Object3D",
|
||||||
|
children: [mappedMesh],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addRenderable(parent, objectNode, meshNode) {
|
||||||
|
const renderable = createRenderableObject(objectNode, meshNode);
|
||||||
|
getOrCreateModelGroup(parent, renderable.name).children.push(renderable);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addObjectsByRange(rawData, parent, start, end, allowedNames) {
|
||||||
|
let currentObject = null;
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
const node = rawData[i];
|
||||||
|
|
||||||
|
if (node?.type === "Object3D") {
|
||||||
|
currentObject = node;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node?.type !== "Mesh") continue;
|
||||||
|
if (allowedNames && !allowedNames.has(node.name)) continue;
|
||||||
|
|
||||||
|
addRenderable(parent, currentObject, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNearestGroup(groups, node) {
|
||||||
|
const [x, , z] = node.position;
|
||||||
|
|
||||||
|
return groups.reduce((nearest, group) => {
|
||||||
|
const [gx, , gz] = group.position;
|
||||||
|
const distance = Math.hypot(x - gx, z - gz);
|
||||||
|
if (!nearest || distance < nearest.distance) {
|
||||||
|
return { group, distance };
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}, null).group;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createResidenceZones(rawData, residence) {
|
||||||
|
const zoneSources = [rawData[830], rawData[874], rawData[892]];
|
||||||
|
const zones = zoneSources.map((sourceNode, index) => {
|
||||||
|
const zone = createGroup(`zone${index + 1}_residence`, sourceNode);
|
||||||
|
residence.children.push(zone);
|
||||||
|
return zone;
|
||||||
|
});
|
||||||
|
|
||||||
|
addObjectsByRange(rawData, zones[0], 831, 873, RESIDENCE_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, zones[1], 875, 891, RESIDENCE_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, zones[2], 893, 942, RESIDENCE_MESH_NAMES);
|
||||||
|
|
||||||
|
for (let i = 14; i <= 23; i++) {
|
||||||
|
const node = rawData[i];
|
||||||
|
if (node?.type === "Mesh" && node.name === "parcebike") {
|
||||||
|
addRenderable(getNearestGroup(zones, node), null, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 25; i <= 58; i++) {
|
||||||
|
const node = rawData[i];
|
||||||
|
if (node?.type === "Mesh" && node.name === "boitesauxlettres") {
|
||||||
|
addRenderable(getNearestGroup(zones, node), null, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return zones;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VEGETATION_MESH_NAMES = new Set(["arbre", "sapin", "buissons"]);
|
||||||
|
const CHAMP_MESH_NAMES = new Set([
|
||||||
|
"champdeble",
|
||||||
|
"champdesoja",
|
||||||
|
"champsdetournesol",
|
||||||
|
]);
|
||||||
|
const FERME_MESH_NAMES = new Set(["buissons", "buisson", "fermeverticale"]);
|
||||||
|
const RESIDENCE_MESH_NAMES = new Set(["immeuble_1", "immeuble_2", "maison1"]);
|
||||||
|
const ENERGIE_MESH_NAMES = new Set([
|
||||||
|
"pyloneelectrique",
|
||||||
|
"eoliennes",
|
||||||
|
"panneausolaire",
|
||||||
|
"generateur",
|
||||||
|
]);
|
||||||
|
const DIRECTION_MESH_NAMES = new Set([
|
||||||
|
"panneauxcentre",
|
||||||
|
"panneauxdomaine",
|
||||||
|
"panneaudircentre",
|
||||||
|
"panneaudirdomaine",
|
||||||
|
"panneaudirfabrik",
|
||||||
|
"panneaudirresidences1",
|
||||||
|
"panneaudirresidences2",
|
||||||
|
"panneauxquartier",
|
||||||
|
]);
|
||||||
|
const LAFABRIK_MESH_NAMES = new Set([
|
||||||
|
"lafabrik",
|
||||||
|
"immeuble_1",
|
||||||
|
"immeuble_2",
|
||||||
|
"maison1",
|
||||||
|
]);
|
||||||
function transformMap() {
|
function transformMap() {
|
||||||
console.log("Reading map_raw.json...");
|
console.log("Reading map_raw.json...");
|
||||||
const rawData = JSON.parse(fs.readFileSync(INPUT_PATH, "utf-8"));
|
const rawData = JSON.parse(fs.readFileSync(INPUT_PATH, "utf-8"));
|
||||||
console.log(`Found ${rawData.length} nodes in raw file`);
|
|
||||||
|
|
||||||
let removedCount = 0;
|
|
||||||
let renamedCount = 0;
|
|
||||||
|
|
||||||
const sceneRaw = rawData.find(
|
const sceneRaw = rawData.find(
|
||||||
(node) => node.name === "Scene" && node.type === "Object3D",
|
(node) => node.name === "Scene" && node.type === "Object3D",
|
||||||
@@ -58,67 +177,99 @@ function transformMap() {
|
|||||||
const terrainRaw = rawData.find(
|
const terrainRaw = rawData.find(
|
||||||
(node) => node.name === "terrain" && node.type === "Object3D",
|
(node) => node.name === "terrain" && node.type === "Object3D",
|
||||||
);
|
);
|
||||||
|
const terrainMesh = rawData.find(
|
||||||
|
(node) => node.name === "terrain" && node.type === "Mesh",
|
||||||
|
);
|
||||||
const blockingRaw = rawData.find(
|
const blockingRaw = rawData.find(
|
||||||
(node) => node.name === "Neutre" && node.type === "Object3D",
|
(node) => node.name === "Neutre" && node.type === "Object3D",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!sceneRaw || !terrainRaw || !blockingRaw) {
|
if (!sceneRaw || !terrainRaw || !terrainMesh || !blockingRaw) {
|
||||||
throw new Error("Missing required Scene, terrain, or Neutre node");
|
throw new Error("Missing required Scene, terrain, or Neutre nodes");
|
||||||
}
|
}
|
||||||
|
|
||||||
const scene = createGroup(sceneRaw);
|
const scene = createGroup("Scene", sceneRaw);
|
||||||
const terrain = createGroup(terrainRaw);
|
const terrain = createGroup("terrain", terrainRaw);
|
||||||
const blocking = createGroup(blockingRaw);
|
const blocking = createGroup("blocking", blockingRaw);
|
||||||
let currentGroup = null;
|
const vegetation = createGroup("vegetation");
|
||||||
|
const agriculture = createGroup("agriculture");
|
||||||
|
const champs = createGroup("champs");
|
||||||
|
const ferme = createGroup("ferme", rawData[4798]);
|
||||||
|
const residence = createGroup("residence");
|
||||||
|
const energie = createGroup("energie", rawData[4800]);
|
||||||
|
const direction = createGroup("direction", rawData[5]);
|
||||||
|
const lafabrik = createGroup("lafabrik", rawData[4873]);
|
||||||
|
const ecole = createGroup("ecole", rawData[4895]);
|
||||||
|
delete ecole.role;
|
||||||
|
const unclassified = createGroup("unclassified");
|
||||||
|
|
||||||
for (const rawNode of rawData) {
|
terrain.children.push(createRenderableObject(terrainRaw, terrainMesh));
|
||||||
if (REMOVED_NODE_NAMES.has(rawNode.name)) {
|
scene.children.push(terrain, blocking);
|
||||||
removedCount++;
|
blocking.children.push(
|
||||||
continue;
|
vegetation,
|
||||||
|
agriculture,
|
||||||
|
residence,
|
||||||
|
energie,
|
||||||
|
direction,
|
||||||
|
lafabrik,
|
||||||
|
ecole,
|
||||||
|
);
|
||||||
|
agriculture.children.push(champs, ferme);
|
||||||
|
|
||||||
|
addObjectsByRange(rawData, direction, 6, 12, DIRECTION_MESH_NAMES);
|
||||||
|
createResidenceZones(rawData, residence);
|
||||||
|
addObjectsByRange(rawData, energie, 61, 96, new Set(["pyloneelectrique"]));
|
||||||
|
addObjectsByRange(rawData, vegetation, 98, 829, VEGETATION_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, agriculture, 944, 944, new Set(["tuyauxlac"]));
|
||||||
|
addObjectsByRange(rawData, champs, 946, 4594, CHAMP_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, ferme, 4595, 4799, FERME_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, vegetation, 4750, 4797, VEGETATION_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, energie, 4801, 4872, ENERGIE_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, lafabrik, 4874, 4894, LAFABRIK_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, direction, 4896, 4897, DIRECTION_MESH_NAMES);
|
||||||
|
addObjectsByRange(rawData, vegetation, 4898, 4997, VEGETATION_MESH_NAMES);
|
||||||
|
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const node = rawData[i];
|
||||||
|
if (node.type !== "Mesh") continue;
|
||||||
|
if (node.name === "mc") continue;
|
||||||
|
if (node.name === "bati-ecole") continue;
|
||||||
|
|
||||||
|
const alreadyClassified = isMeshClassified(scene, node);
|
||||||
|
if (!alreadyClassified) {
|
||||||
|
addRenderable(unclassified, null, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawNode.name === "Scene" || rawNode.name === "Neutre") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawNode.name === "terrain" && rawNode.type === "Object3D") {
|
|
||||||
currentGroup = terrain;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawNode.type === "Object3D") {
|
|
||||||
currentGroup = createGroup(rawNode);
|
|
||||||
blocking.children.push(currentGroup);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mappedNode = mapMeshName(rawNode);
|
|
||||||
if (mappedNode.name !== rawNode.name) {
|
|
||||||
renamedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawNode.name === "terrain" && currentGroup === terrain) {
|
|
||||||
terrain.children.push(mappedNode);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentGroup) {
|
|
||||||
currentGroup.children.push(mappedNode);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocking.children.push(mappedNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.children = [terrain, blocking];
|
if (unclassified.children.length > 0) {
|
||||||
|
blocking.children.push(unclassified);
|
||||||
console.log(`\nTransformation complete:`);
|
}
|
||||||
console.log(` - Removed ${removedCount} mc/ROOT nodes`);
|
|
||||||
console.log(` - Renamed ${renamedCount} mesh nodes`);
|
|
||||||
console.log(` - Output: hierarchical Scene root`);
|
|
||||||
|
|
||||||
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(scene, null, 2));
|
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(scene, null, 2));
|
||||||
console.log(`\nWritten to ${OUTPUT_PATH}`);
|
console.log(`Written hierarchical map to ${OUTPUT_PATH}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameTransform(a, b) {
|
||||||
|
return (
|
||||||
|
a.name === (MESH_NAME_MAPPINGS[b.name] ?? b.name) &&
|
||||||
|
JSON.stringify(a.position) === JSON.stringify(b.position) &&
|
||||||
|
JSON.stringify(a.rotation) === JSON.stringify(b.rotation) &&
|
||||||
|
JSON.stringify(a.scale) === JSON.stringify(b.scale)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMeshClassified(root, rawMeshNode) {
|
||||||
|
const stack = [...(root.children ?? [])];
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const node = stack.pop();
|
||||||
|
if (node.type === "Mesh" && isSameTransform(node, rawMeshNode)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
stack.push(...(node.children ?? []));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
transformMap();
|
transformMap();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface MapNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface HierarchicalMapNode extends MapNode {
|
export interface HierarchicalMapNode extends MapNode {
|
||||||
|
role?: "group";
|
||||||
children?: HierarchicalMapNode[];
|
children?: HierarchicalMapNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ function isHierarchicalMapNode(value: unknown): value is HierarchicalMapNode {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("role" in value && value.role !== undefined && value.role !== "group") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!("children" in value)) {
|
if (!("children" in value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -52,6 +56,10 @@ function flattenMapNode(node: HierarchicalMapNode): MapNode[] {
|
|||||||
};
|
};
|
||||||
const childNodes = node.children?.flatMap(flattenMapNode) ?? [];
|
const childNodes = node.children?.flatMap(flattenMapNode) ?? [];
|
||||||
|
|
||||||
|
if (node.role === "group") {
|
||||||
|
return childNodes;
|
||||||
|
}
|
||||||
|
|
||||||
return [mapNode, ...childNodes];
|
return [mapNode, ...childNodes];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user