Warning, /firebird/docs/geometry-rules.md is written in an unsupported language. File is not indexed.
0001 # Geometry Editing Rules
0002
0003 Firebird uses a powerful rule-based system to customize detector geometry appearance.
0004 Rules control colors, materials, merging behavior, and outlines for different detector components.
0005
0006 ## Overview
0007
0008 When geometry is loaded from ROOT files, it consists of a hierarchical tree of Three.js `Object3D` nodes
0009 (Groups and Meshes). The geometry editing system allows you to:
0010
0011 - **Match nodes** by name patterns (glob-style wildcards)
0012 - **Apply styling** (colors, materials, transparency)
0013 - **Merge geometries** for better performance
0014 - **Create outlines** for visual clarity
0015 - **Process hierarchically** ("the rest" rules for unmatched nodes)
0016
0017 ## Rule Structure
0018
0019 Rules are organized in **RuleSets**, where each ruleset targets specific detectors:
0020
0021 ```typescript
0022 {
0023 name: "DIRC*", // Match detector by name (glob pattern)
0024 // or
0025 names: ["DIRC*", "RICH*"], // Match multiple detectors
0026
0027 rules: [
0028 // Array of rules applied in order
0029 ]
0030 }
0031 ```
0032
0033 ### Basic Rule Properties
0034
0035 ```typescript
0036 interface EditThreeNodeRule {
0037 // Node selection
0038 patterns?: string[] | string; // Glob patterns to match nodes
0039
0040 // Geometry handling
0041 merge?: boolean; // Merge matched meshes (default: true)
0042 newName?: string; // Name for merged mesh
0043 deleteOrigins?: boolean; // Remove original meshes after merge (default: true)
0044 cleanupNodes?: boolean; // Remove empty groups (default: true)
0045
0046 // Styling
0047 color?: ColorRepresentation; // Hex color (e.g., 0xFF0000)
0048 material?: Material; // Three.js material
0049
0050 // Outlines
0051 outline?: boolean; // Create edge outlines (default: true)
0052 outlineThresholdAngle?: number; // Edge detection angle in degrees (default: 40)
0053 outlineColor?: ColorRepresentation;
0054
0055 // Advanced
0056 applyToDescendants?: boolean; // Apply to children of matched nodes
0057 simplifyMeshes?: boolean; // Reduce vertex count
0058 simplifyRatio?: number; // Simplification ratio (default: 0.7)
0059 }
0060 ```
0061
0062 ## Pattern Matching
0063
0064 Patterns use glob-style matching (powered by [outmatch](https://github.com/axtgr/outmatch)):
0065
0066 | Pattern | Matches |
0067 |---------|---------|
0068 | `*` | Any single path segment |
0069 | `**` | Any number of path segments |
0070 | `**/name*` | Any node whose name starts with "name" |
0071 | `**/v_upstream*` | Nodes like `v_upstream_coating`, `v_upstream_wall` |
0072 | `**/*box*` | Any node containing "box" in its name |
0073
0074 ### Path Structure
0075
0076 Node paths are built from the hierarchy:
0077 ```
0078 BeamPipe_assembly/v_upstream_coating/Left
0079 ↑ ↑
0080 parent name child name
0081 ```
0082
0083 The pattern `**/v_upstream*` matches `v_upstream_coating` but NOT `Left`.
0084 To match descendants, use `applyToDescendants: true` (default when `merge: false`).
0085
0086 ## Rule Processing Order
0087
0088 Rules are processed **in order**. Earlier rules take precedence:
0089
0090 ```typescript
0091 rules: [
0092 // Rule 1: Process specific patterns first
0093 {
0094 patterns: ["**/mirror*"],
0095 color: SILVER,
0096 merge: true
0097 },
0098 // Rule 2: "The rest" - no pattern means all remaining nodes
0099 {
0100 color: GREEN_100,
0101 merge: false,
0102 outline: true
0103 }
0104 ]
0105 ```
0106
0107 ### Skip Flags and Hierarchical Processing
0108
0109 When a node is processed, it's marked with a skip flag. Subsequent rules respect this:
0110
0111 1. **Individual skip**: Processed meshes are skipped by later rules
0112 2. **Hierarchical skip**: If a parent is processed, descendants are skipped by "the rest" rules
0113 3. **Outlines**: Created outlines are automatically marked to prevent "outline of outline"
0114
0115 ## Working with Nested Nodes
0116
0117 Understanding how rules interact with hierarchical geometry is crucial for effective styling.
0118
0119 ### Geometry Tree Structure
0120
0121 Detector geometry forms a tree of `Object3D` nodes:
0122
0123 ```
0124 BeamPipe_assembly (Group)
0125 ├── v_upstream_coating (Group)
0126 │ ├── Left (Mesh) ← has geometry, can be styled
0127 │ └── Right (Mesh) ← has geometry, can be styled
0128 ├── v_downstream_section (Mesh)
0129 └── center_pipe (Mesh)
0130 ```
0131
0132 **Key distinction:**
0133 - **Groups** - containers without geometry, used for organization
0134 - **Meshes** - have actual geometry that gets rendered
0135
0136 ### Pattern Matching Depth
0137
0138 Patterns match based on the **full path** from root:
0139
0140 ```typescript
0141 // Given this structure:
0142 // BeamPipe_assembly/v_upstream_coating/Left
0143
0144 // Pattern "**/v_upstream*" matches:
0145 "BeamPipe_assembly/v_upstream_coating" ✓ (name starts with v_upstream)
0146
0147 // But does NOT match:
0148 "BeamPipe_assembly/v_upstream_coating/Left" ✗ (name is "Left", not "v_upstream*")
0149 ```
0150
0151 ### The `applyToDescendants` Option
0152
0153 When a pattern matches a **Group** (not a Mesh), you need to decide what happens to its children:
0154
0155 ```typescript
0156 {
0157 patterns: ["**/v_upstream_coating"],
0158 color: 0xFF0000,
0159 merge: false,
0160 applyToDescendants: true // ← Default when merge=false
0161 }
0162 ```
0163
0164 | `applyToDescendants` | Behavior |
0165 |---------------------|----------|
0166 | `true` (default) | Pattern matches Group → all descendant Meshes get styled |
0167 | `false` | Pattern matches Group → nothing styled (Groups have no geometry) |
0168
0169 **Example - styling a branch:**
0170
0171 ```typescript
0172 // Style v_upstream_coating AND all its children (Left, Right)
0173 {
0174 patterns: ["**/v_upstream_coating"],
0175 color: INDIGO_200,
0176 merge: false
0177 // applyToDescendants: true is the default
0178 }
0179
0180 // Result:
0181 // v_upstream_coating (Group) - marked as processed
0182 // ├── Left (Mesh) - colored INDIGO_200
0183 // └── Right (Mesh) - colored INDIGO_200
0184 ```
0185
0186 ### "The Rest" Rules and Hierarchical Skip
0187
0188 A rule **without patterns** is called a "the rest" rule - it processes everything not yet handled:
0189
0190 ```typescript
0191 rules: [
0192 // Rule 1: Specific pattern
0193 { patterns: ["**/v_upstream*"], color: RED, merge: false },
0194
0195 // Rule 2: "The rest" - no patterns
0196 { color: GREEN, merge: false }
0197 ]
0198 ```
0199
0200 **Hierarchical skip** ensures that once a branch is processed, "the rest" rules skip the entire branch:
0201
0202 ```
0203 Processing flow:
0204
0205 1. Rule 1 matches "v_upstream_coating"
0206 → Marks v_upstream_coating as processed
0207 → Styles Left and Right (applyToDescendants=true)
0208 → Marks Left and Right as processed
0209
0210 2. Rule 2 ("the rest") runs
0211 → Checks each mesh: "Is it in a processed branch?"
0212 → Left: parent v_upstream_coating is processed → SKIP
0213 → Right: parent v_upstream_coating is processed → SKIP
0214 → center_pipe: not in processed branch → style GREEN
0215 → v_downstream_section: not in processed branch → style GREEN
0216 ```
0217
0218 ### Common Pitfalls
0219
0220 #### Pitfall 1: Pattern doesn't match children
0221
0222 ```typescript
0223 // WRONG: Only matches v_upstream_coating, not its children
0224 { patterns: ["**/v_upstream_coating"], merge: true }
0225 // Result: Tries to merge just the Group (no geometry!)
0226
0227 // CORRECT: Match the Group with applyToDescendants
0228 { patterns: ["**/v_upstream_coating"], merge: false, applyToDescendants: true }
0229 // Result: Styles all children of v_upstream_coating
0230 ```
0231
0232 #### Pitfall 2: Children processed twice
0233
0234 ```typescript
0235 // PROBLEMATIC:
0236 rules: [
0237 { patterns: ["**/v_upstream*"], color: RED }, // Matches v_upstream_coating
0238 { patterns: ["**/Left", "**/Right"], color: BLUE } // Also matches children!
0239 ]
0240 // Result: Left and Right get BLUE (second rule overwrites)
0241
0242 // BETTER: Use one pattern and rely on applyToDescendants
0243 rules: [
0244 { patterns: ["**/v_upstream*"], color: RED, merge: false }
0245 ]
0246 // Result: v_upstream_coating AND its children get RED
0247 ```
0248
0249 #### Pitfall 3: "The rest" processes already-styled nodes
0250
0251 ```typescript
0252 // WRONG ORDER:
0253 rules: [
0254 { color: GREEN, merge: false }, // "The rest" FIRST - styles everything!
0255 { patterns: ["**/mirror*"], color: SILVER } // Never runs - everything already processed
0256 ]
0257
0258 // CORRECT ORDER:
0259 rules: [
0260 { patterns: ["**/mirror*"], color: SILVER }, // Specific patterns FIRST
0261 { color: GREEN, merge: false } // "The rest" LAST
0262 ]
0263 ```
0264
0265 #### Pitfall 4: Outline of outline
0266
0267 When creating outlines, the outline object is added to the scene. Without protection, a subsequent rule might try to outline the outline:
0268
0269 ```typescript
0270 // This is handled automatically!
0271 // Outlines are marked with geometryEditingSkipRules = true
0272 // They will be skipped by subsequent rules
0273 ```
0274
0275 ### Advanced: Multiple Nesting Levels
0276
0277 For deeply nested structures:
0278
0279 ```
0280 DRICH_assembly (Group)
0281 ├── sector_0 (Group)
0282 │ ├── mirror_panel (Group)
0283 │ │ ├── mirror_surface (Mesh)
0284 │ │ └── mirror_backing (Mesh)
0285 │ └── sensor_array (Group)
0286 │ ├── sensor_0 (Mesh)
0287 │ └── sensor_1 (Mesh)
0288 └── sector_1 (Group)
0289 └── ...
0290 ```
0291
0292 **Strategy 1: Match deepest level first**
0293 ```typescript
0294 rules: [
0295 // Most specific first
0296 { patterns: ["**/mirror_surface*"], color: SILVER, merge: true, newName: "mirrors" },
0297 { patterns: ["**/sensor_*"], color: TEAL_200, merge: true, newName: "sensors" },
0298 // Everything else
0299 { color: GREEN_100, merge: false }
0300 ]
0301 ```
0302
0303 **Strategy 2: Match by branch**
0304 ```typescript
0305 rules: [
0306 // Match parent groups, style all descendants
0307 { patterns: ["**/mirror_panel"], color: SILVER, merge: false },
0308 { patterns: ["**/sensor_array"], color: TEAL_200, merge: false },
0309 // Everything else
0310 { color: GREEN_100, merge: false }
0311 ]
0312 ```
0313
0314 **Strategy 3: Merge entire branches**
0315 ```typescript
0316 rules: [
0317 // Merge all meshes under mirror_panel into one
0318 { patterns: ["**/mirror_panel/**"], merge: true, newName: "mirrors" },
0319 // Merge all sensors
0320 { patterns: ["**/sensor_array/**"], merge: true, newName: "sensors" }
0321 ]
0322 ```
0323
0324 ### Visualizing the Processing
0325
0326 Here's a complete example showing the processing flow:
0327
0328 ```typescript
0329 // Geometry:
0330 // BeamPipe_assembly
0331 // ├── v_upstream_coating
0332 // │ ├── Left
0333 // │ └── Right
0334 // ├── v_downstream_section
0335 // └── center_pipe
0336
0337 {
0338 name: "BeamPipe_assembly*",
0339 rules: [
0340 // Rule 1: Match v_upstream branch
0341 {
0342 patterns: ["**/v_upstream*"],
0343 color: INDIGO_200,
0344 merge: false,
0345 outline: true
0346 },
0347 // Rule 2: The rest
0348 {
0349 color: INDIGO_80,
0350 merge: false,
0351 outline: true
0352 }
0353 ]
0354 }
0355
0356 // Processing steps:
0357 //
0358 // 1. Clear all skip flags on BeamPipe_assembly tree
0359 //
0360 // 2. Rule 1 executes:
0361 // - Pattern "**/v_upstream*" matches "v_upstream_coating"
0362 // - applyToDescendants=true (default)
0363 // - Traverse v_upstream_coating:
0364 // - Left (Mesh): color=INDIGO_200, create outline, mark processed
0365 // - Right (Mesh): color=INDIGO_200, create outline, mark processed
0366 // - Mark v_upstream_coating as processed
0367 //
0368 // 3. Rule 2 ("the rest") executes:
0369 // - Traverse BeamPipe_assembly looking for unprocessed meshes:
0370 // - v_upstream_coating: skip (processed)
0371 // - Left: skip (in processed branch - parent is processed)
0372 // - Right: skip (in processed branch)
0373 // - Left_outline: skip (marked when created)
0374 // - Right_outline: skip (marked when created)
0375 // - v_downstream_section: NOT in processed branch
0376 // → color=INDIGO_80, create outline, mark processed
0377 // - center_pipe: NOT in processed branch
0378 // → color=INDIGO_80, create outline, mark processed
0379 //
0380 // Final result:
0381 // ├── v_upstream_coating (processed)
0382 // │ ├── Left ─────────── INDIGO_200 + outline
0383 // │ ├── Left_outline ─── (auto-created)
0384 // │ ├── Right ────────── INDIGO_200 + outline
0385 // │ └── Right_outline ── (auto-created)
0386 // ├── v_downstream_section ─ INDIGO_80 + outline
0387 // ├── v_downstream_section_outline
0388 // ├── center_pipe ────────── INDIGO_80 + outline
0389 // └── center_pipe_outline
0390 ```
0391
0392 ## Examples
0393
0394 ### Simple Color Change
0395
0396 ```typescript
0397 {
0398 name: "HcalBarrel*",
0399 rules: [
0400 {
0401 color: 0x90CAF9, // Light blue
0402 merge: true,
0403 outline: true
0404 }
0405 ]
0406 }
0407 ```
0408
0409 ### Multiple Patterns with Different Styles
0410
0411 ```typescript
0412 {
0413 name: "DRICH*",
0414 rules: [
0415 // Mirrors get special treatment
0416 {
0417 patterns: ["**/DRICH_mirror*"],
0418 color: SILVER,
0419 merge: true,
0420 outline: false,
0421 newName: "DRICH_mirror"
0422 },
0423 // PDUs in teal
0424 {
0425 patterns: ["**/DRICH*pdu*"],
0426 color: TEAL_200,
0427 merge: true,
0428 newName: "DRICH_pdu"
0429 },
0430 // Everything else in light green
0431 {
0432 color: GREEN_100,
0433 merge: false,
0434 outline: true
0435 }
0436 ]
0437 }
0438 ```
0439
0440 ### Transparent Material
0441
0442 ```typescript
0443 {
0444 name: "InnerTrackerSupport_assembly*",
0445 rules: [
0446 {
0447 material: new THREE.MeshStandardMaterial({
0448 color: 0x878F99, // Titanium
0449 roughness: 0.4,
0450 metalness: 0.2,
0451 transparent: true,
0452 opacity: 0.7,
0453 side: THREE.DoubleSide
0454 }),
0455 outline: true,
0456 outlineColor: 0xDBE4EB, // Chrome
0457 merge: true
0458 }
0459 ]
0460 }
0461 ```
0462
0463 ### Glass-like Material (DIRC)
0464
0465 ```typescript
0466 {
0467 patterns: ["**/*box*", "**/*prism*"],
0468 material: new THREE.MeshPhysicalMaterial({
0469 color: 0xA5D6A7, // Green
0470 metalness: 0.3,
0471 roughness: 0.2,
0472 envMapIntensity: 0.5,
0473 clearcoat: 0.8,
0474 transparent: true,
0475 opacity: 0.5,
0476 reflectivity: 0.2,
0477 ior: 0.9,
0478 side: THREE.DoubleSide
0479 }),
0480 newName: "DIRC_barAndPrisms"
0481 }
0482 ```
0483
0484 ### Hierarchical Processing (BeamPipe Example)
0485
0486 ```typescript
0487 {
0488 name: "BeamPipe_assembly*",
0489 rules: [
0490 // Match v_upstream nodes AND all their children
0491 {
0492 patterns: ["**/v_upstream*"],
0493 color: 0x9FA8DA, // Indigo 200
0494 merge: false, // Keep individual meshes
0495 outline: true
0496 // applyToDescendants defaults to true
0497 },
0498 // "The rest" - skips v_upstream branch entirely
0499 {
0500 color: 0xD1D9F0, // Indigo 80
0501 merge: false,
0502 outline: true
0503 }
0504 ]
0505 }
0506 ```
0507
0508 ## Built-in Color Palette
0509
0510 Firebird provides a curated color palette in `cool3-geometry-ruleset.ts`:
0511
0512 ### Cool Colors (Light/Pastel)
0513 | Constant | Hex | Use Case |
0514 |----------|-----|----------|
0515 | `BLUE_50` | `0xE3F2FD` | Very light backgrounds |
0516 | `CYAN_50` | `0xE0F7FA` | Light cyan accents |
0517 | `INDIGO_80` | `0xD1D9F0` | Light indigo |
0518 | `INDIGO_150` | `0xB2BBE0` | Medium-light indigo |
0519 | `GREEN_100` | `0xC8E6C9` | Light green (PID) |
0520 | `YELLOW_100` | `0xFFF9C4` | Light yellow (tracking) |
0521
0522 ### Medium Colors
0523 | Constant | Hex | Use Case |
0524 |----------|-----|----------|
0525 | `AMBER_200` | `0xFFE082` | Tracking detectors |
0526 | `BLUE_200` | `0x90CAF9` | HCAL |
0527 | `TEAL_200` | `0x80CBC4` | TOF components |
0528 | `INDIGO_200` | `0x9FA8DA` | ECAL |
0529 | `ORANGE_200` | `0xFFCC80` | Roman pots |
0530
0531 ### Metallic Colors
0532 | Constant | Hex | Use Case |
0533 |----------|-----|----------|
0534 | `SILVER` | `0xC0C0C0` | Mirrors, rails |
0535 | `CHROME` | `0xDBE4EB` | Polished metal |
0536 | `STEEL_BLUE` | `0x4682B4` | Beam pipes |
0537 | `TITANIUM` | `0x878F99` | Supports |
0538 | `COPPER` | `0xB87333` | Conductors |
0539
0540 ### Color Scheme Philosophy
0541
0542 The COOL3 theme follows detector-type conventions:
0543
0544 | Detector Type | Color Family | Examples |
0545 |---------------|--------------|----------|
0546 | **Tracking** | Yellowish-Orange | Vertex, Si Trackers, MPGD |
0547 | **PID** | Greenish | TOF, DIRC, RICH |
0548 | **ECAL** | Pink/Violet | Forward, Barrel, Backward |
0549 | **HCAL** | Bluish | LFHCAL, HcalBarrel |
0550 | **Flux Return** | Grey | FluxBarrel, FluxEndcap |
0551 | **Beam Pipes** | Blue Metallic | Electron pipe |
0552 | **Magnets** | Neutral/Light | Solenoid, Beamline magnets |
0553
0554 ## Creating Custom Themes
0555
0556 To create a custom theme:
0557
0558 1. **Create a new file** in `src/app/theme/`:
0559
0560 ```typescript
0561 // my-theme-geometry-ruleset.ts
0562 import * as THREE from "three";
0563
0564 // Define your colors
0565 export const MY_PRIMARY = 0x3498DB;
0566 export const MY_SECONDARY = 0x2ECC71;
0567
0568 // Export your ruleset
0569 export const myThemeRules = [
0570 {
0571 name: "MyDetector*",
0572 rules: [
0573 { color: MY_PRIMARY, merge: true, outline: true }
0574 ]
0575 },
0576 // ... more rules
0577 ];
0578 ```
0579
0580 2. **Register the theme** in the geometry processor or configuration.
0581
0582 ## Performance Considerations
0583
0584 ### Merge for Performance
0585
0586 Merging reduces draw calls significantly:
0587
0588 ```typescript
0589 // Good for performance - single merged mesh
0590 { merge: true, newName: "HCAL_merged" }
0591
0592 // More flexible but slower - individual meshes
0593 { merge: false }
0594 ```
0595
0596 ### Outline Threshold
0597
0598 Higher threshold = fewer edges = better performance:
0599
0600 ```typescript
0601 {
0602 outline: true,
0603 outlineThresholdAngle: 60 // Only show sharp edges
0604 }
0605 ```
0606
0607 ### Simplification
0608
0609 For complex geometries:
0610
0611 ```typescript
0612 {
0613 simplifyMeshes: true,
0614 simplifyRatio: 0.5 // Keep 50% of vertices
0615 }
0616 ```
0617
0618 ## Debugging Rules
0619
0620 ### Check Console Output
0621
0622 The geometry processor logs timing information:
0623
0624 ```
0625 [processRuleSets] Applying 25 theme rules...
0626 [processRuleSets] Applying rules took >0.5s: 1234.5 for DRICH_assembly
0627 ```
0628
0629 ### Verify Patterns
0630
0631 Use the browser console to test patterns:
0632
0633 ```javascript
0634 // In browser dev tools
0635 import outmatch from 'outmatch';
0636 const pattern = outmatch('**/v_upstream*');
0637 pattern('BeamPipe_assembly/v_upstream_coating'); // true
0638 pattern('BeamPipe_assembly/v_upstream_coating/Left'); // false
0639 ```
0640
0641 ## API Reference
0642
0643 ### Main Functions
0644
0645 ```typescript
0646 // Clear skip flags before processing
0647 clearGeometryEditingFlags(root: Object3D): void
0648
0649 // Apply a single rule to a node
0650 editThreeNodeContent(node: Object3D, rule: EditThreeNodeRule): void
0651
0652 // Check if node was already processed
0653 isAlreadyProcessed(obj: Object3D): boolean
0654
0655 // Check if node or any ancestor was processed
0656 isInProcessedBranch(obj: Object3D): boolean
0657 ```
0658
0659 ### ThreeGeometryProcessor
0660
0661 ```typescript
0662 class ThreeGeometryProcessor {
0663 // Apply rulesets to detectors
0664 processRuleSets(
0665 ruleSets: DetectorThreeRuleSet[],
0666 detectors: Subdetector[]
0667 ): void
0668 }
0669 ```
0670
0671 ### Loading Rules from JSON
0672
0673 Rules can be loaded from JSONC files:
0674
0675 ```typescript
0676 import { ruleSetsFromObj } from './three-geometry.processor';
0677
0678 // Parse JSON with material support
0679 const ruleSets = ruleSetsFromObj(jsonData);
0680 ```
0681
0682 JSON format supports `materialJson` for Three.js materials:
0683
0684 ```json
0685 {
0686 "name": "MyDetector*",
0687 "rules": [
0688 {
0689 "patterns": ["**/*box*"],
0690 "color": "0xA5D6A7",
0691 "materialJson": {
0692 "type": "MeshStandardMaterial",
0693 "color": 10809767,
0694 "roughness": 0.5,
0695 "metalness": 0.3
0696 }
0697 }
0698 ]
0699 }
0700 ```