diff --git a/source/engine/main.js b/source/engine/main.js index f10c482..4dc7308 100644 --- a/source/engine/main.js +++ b/source/engine/main.js @@ -54,7 +54,7 @@ import { MeshInstanceId, MeshInstance } from './model/meshinstance.js'; import { IsEmptyMesh, CalculateTriangleNormal, TransformMesh, FlipMeshTrianglesOrientation } from './model/meshutils.js'; import { Model } from './model/model.js'; import { FinalizeModel, CheckModel } from './model/modelfinalization.js'; -import { IsModelEmpty, GetBoundingBox, GetTopology, IsTwoManifold, HasDefaultMaterial, ReplaceDefaultMaterialColor } from './model/modelutils.js'; +import { IsModelEmpty, GetBoundingBox, GetTopology, IsTwoManifold, HasDefaultMaterial, ReplaceDefaultMaterialsColor } from './model/modelutils.js'; import { Node } from './model/node.js'; import { Object3D, ModelObject3D } from './model/object.js'; import { Property, PropertyGroup, PropertyToString, PropertyType } from './model/property.js'; @@ -63,9 +63,9 @@ import { TopologyVertex, TopologyEdge, TopologyTriangleEdge, TopologyTriangle, T import { Triangle } from './model/triangle.js'; import { Unit } from './model/unit.js'; import { ParameterListBuilder, ParameterListParser, CreateUrlBuilder, CreateUrlParser, CreateModelUrlParameters, ParameterConverter } from './parameters/parameterlist.js'; -import { ModelToThreeConversionParams, ModelToThreeConversionOutput, ThreeConversionStateHandler, ThreeNodeTree, ThreeMaterialHandler, ThreeMeshMaterialHandler, ConvertModelToThreeObject } from './threejs/threeconverter.js'; +import { ModelToThreeConversionParams, ModelToThreeConversionOutput, ThreeConversionStateHandler, ThreeNodeTree, ThreeMaterialHandler, ThreeMeshMaterialHandler, ConvertModelToThreeObject, MaterialGeometryType } from './threejs/threeconverter.js'; import { ThreeModelLoader } from './threejs/threemodelloader.js'; -import { ThreeColorConverter, ThreeLinearToSRGBColorConverter, ThreeSRGBToLinearColorConverter, HasHighpDriverIssue, GetShadingType, ConvertThreeColorToColor, ConvertColorToThreeColor, ConvertThreeGeometryToMesh, DisposeThreeObjects, ShadingType } from './threejs/threeutils.js'; +import { ThreeColorConverter, ThreeLinearToSRGBColorConverter, ThreeSRGBToLinearColorConverter, HasHighpDriverIssue, GetShadingType, ConvertThreeColorToColor, ConvertColorToThreeColor, ConvertThreeGeometryToMesh, CreateHighlightMaterial, CreateHighlightMaterials, DisposeThreeObjects, ShadingType } from './threejs/threeutils.js'; import { Camera, CameraIsEqual3D, NavigationMode, ProjectionMode } from './viewer/camera.js'; import { GetIntegerFromStyle, GetDomElementExternalWidth, GetDomElementExternalHeight, GetDomElementInnerDimensions, GetDomElementClientCoordinates, CreateDomElement, AddDomElement, AddDiv, ClearDomElement, InsertDomElementBefore, InsertDomElementAfter, ShowDomElement, IsDomElementVisible, SetDomElementWidth, SetDomElementHeight, GetDomElementOuterWidth, GetDomElementOuterHeight, SetDomElementOuterWidth, SetDomElementOuterHeight, CreateDiv } from './viewer/domutils.js'; import { EmbeddedViewer, Init3DViewerFromUrlList, Init3DViewerFromFileList, Init3DViewerElements } from './viewer/embeddedviewer.js'; @@ -254,7 +254,7 @@ export { GetTopology, IsTwoManifold, HasDefaultMaterial, - ReplaceDefaultMaterialColor, + ReplaceDefaultMaterialsColor, Node, Object3D, ModelObject3D, @@ -286,6 +286,7 @@ export { ThreeMaterialHandler, ThreeMeshMaterialHandler, ConvertModelToThreeObject, + MaterialGeometryType, ThreeModelLoader, ThreeColorConverter, ThreeLinearToSRGBColorConverter, @@ -295,6 +296,8 @@ export { ConvertThreeColorToColor, ConvertColorToThreeColor, ConvertThreeGeometryToMesh, + CreateHighlightMaterial, + CreateHighlightMaterials, DisposeThreeObjects, ShadingType, Camera, diff --git a/source/engine/model/modelutils.js b/source/engine/model/modelutils.js index 064ba3e..8bb131d 100644 --- a/source/engine/model/modelutils.js +++ b/source/engine/model/modelutils.js @@ -106,7 +106,7 @@ export function HasDefaultMaterial (model) return false; } -export function ReplaceDefaultMaterialColor (model, color) +export function ReplaceDefaultMaterialsColor (model, color) { for (let i = 0; i < model.MaterialCount (); i++) { let material = model.GetMaterial (i); diff --git a/source/engine/threejs/threeconverter.js b/source/engine/threejs/threeconverter.js index 9a113b0..1625eb2 100644 --- a/source/engine/threejs/threeconverter.js +++ b/source/engine/threejs/threeconverter.js @@ -8,6 +8,12 @@ import { ConvertColorToThreeColor, GetShadingType, ShadingType } from './threeut import * as THREE from 'three'; +export const MaterialGeometryType = +{ + Line : 1, + Face : 2 +}; + export class ModelToThreeConversionParams { constructor () @@ -20,7 +26,7 @@ export class ModelToThreeConversionOutput { constructor () { - this.defaultMaterial = null; + this.defaultMaterials = []; this.objectUrls = []; } } @@ -97,12 +103,6 @@ export class ThreeNodeTree } } -export const MaterialGeometryType = -{ - Line : 1, - Face : 2 -}; - export class ThreeMaterialHandler { constructor (model, stateHandler, conversionParams, conversionOutput) @@ -205,7 +205,7 @@ export class ThreeMaterialHandler }); if (material.isDefault) { - this.conversionOutput.defaultMaterial = threeMaterial; + this.conversionOutput.defaultMaterials.push (threeMaterial); } return threeMaterial; @@ -226,7 +226,7 @@ export class ThreeMaterialHandler let threeMaterial = new THREE.LineBasicMaterial (materialParams); if (material.isDefault) { - this.conversionOutput.defaultMaterial = threeMaterial; + this.conversionOutput.defaultMaterials.push (threeMaterial); } return threeMaterial; diff --git a/source/engine/threejs/threemodelloader.js b/source/engine/threejs/threemodelloader.js index 73037a6..7be28f6 100644 --- a/source/engine/threejs/threemodelloader.js +++ b/source/engine/threejs/threemodelloader.js @@ -12,7 +12,7 @@ export class ThreeModelLoader { this.importer = new Importer (); this.inProgress = false; - this.defaultMaterial = null; + this.defaultMaterials = null; this.objectUrls = null; this.hasHighpDriverIssue = HasHighpDriverIssue (); } @@ -60,7 +60,7 @@ export class ThreeModelLoader callbacks.onTextureLoaded (); }, onModelLoaded : (threeObject) => { - this.defaultMaterial = output.defaultMaterial; + this.defaultMaterials = output.defaultMaterials; this.objectUrls = output.objectUrls; if (importResult.upVector === Direction.X) { let rotation = new THREE.Quaternion ().setFromAxisAngle (new THREE.Vector3 (0.0, 0.0, 1.0), Math.PI / 2.0); @@ -86,15 +86,19 @@ export class ThreeModelLoader return this.importer; } - GetDefaultMaterial () + GetDefaultMaterials () { - return this.defaultMaterial; + return this.defaultMaterials; } - ReplaceDefaultMaterialColor (defaultColor) + ReplaceDefaultMaterialsColor (defaultColor) { - if (this.defaultMaterial !== null && !this.defaultMaterial.vertexColors) { - this.defaultMaterial.color = ConvertColorToThreeColor (defaultColor); + if (this.defaultMaterials !== null) { + for (let defaultMaterial of this.defaultMaterials) { + if (!defaultMaterial.vertexColors) { + defaultMaterial.color = ConvertColorToThreeColor (defaultColor); + } + } } } diff --git a/source/engine/threejs/threeutils.js b/source/engine/threejs/threeutils.js index 928850c..132373b 100644 --- a/source/engine/threejs/threeutils.js +++ b/source/engine/threejs/threeutils.js @@ -210,6 +210,48 @@ export function ConvertThreeGeometryToMesh (threeGeometry, materialIndex, colorC return mesh; } +export function CreateHighlightMaterial (originalMaterial, highlightColor, withPolygonOffset) +{ + let material = null; + if (originalMaterial.type === 'MeshPhongMaterial') { + material = new THREE.MeshPhongMaterial ({ + color : ConvertColorToThreeColor (highlightColor), + side : THREE.DoubleSide + }); + } else if (originalMaterial.type === 'MeshStandardMaterial') { + material = new THREE.MeshStandardMaterial ({ + color : ConvertColorToThreeColor (highlightColor), + side : THREE.DoubleSide + }); + } else if (originalMaterial.type === 'LineBasicMaterial') { + material = new THREE.LineBasicMaterial ({ + color : ConvertColorToThreeColor (highlightColor) + }); + } + if (material !== null && withPolygonOffset) { + material.polygonOffset = true; + material.polygonOffsetUnit = 1; + material.polygonOffsetFactor = 1; + } + return material; +} + +export function CreateHighlightMaterials (originalMaterials, highlightColor, withPolygonOffset) +{ + let typeToHighlightMaterial = new Map (); + let highlightMaterials = []; + for (let originalMaterial of originalMaterials) { + if (typeToHighlightMaterial.has (originalMaterial.type)) { + highlightMaterials.push (typeToHighlightMaterial.get (originalMaterial.type)); + continue; + } + let highlightMaterial = CreateHighlightMaterial (originalMaterial, highlightColor, withPolygonOffset); + typeToHighlightMaterial.set (originalMaterial.type, highlightMaterial); + highlightMaterials.push (highlightMaterial); + } + return highlightMaterials; +} + export function DisposeThreeObjects (mainObject) { if (mainObject === null) { diff --git a/source/engine/viewer/shadingmodel.js b/source/engine/viewer/shadingmodel.js index 8fa5c16..d732261 100644 --- a/source/engine/viewer/shadingmodel.js +++ b/source/engine/viewer/shadingmodel.js @@ -1,6 +1,6 @@ import { SubCoord3D } from '../geometry/coord3d.js'; import { ProjectionMode } from '../viewer/camera.js'; -import { ConvertColorToThreeColor, ShadingType } from '../threejs/threeutils.js'; +import { ShadingType } from '../threejs/threeutils.js'; import * as THREE from 'three'; @@ -99,26 +99,4 @@ export class ShadingModel const lightDir = SubCoord3D (camera.eye, camera.center); this.directionalLight.position.set (lightDir.x, lightDir.y, lightDir.z); } - - CreateHighlightMaterial (highlightColor, withOffset) - { - let material = null; - if (this.type === ShadingType.Phong) { - material = new THREE.MeshPhongMaterial ({ - color : ConvertColorToThreeColor (highlightColor), - side : THREE.DoubleSide - }); - } else if (this.type === ShadingType.Physical) { - material = new THREE.MeshStandardMaterial ({ - color : ConvertColorToThreeColor (highlightColor), - side : THREE.DoubleSide - }); - } - if (material !== null && withOffset) { - material.polygonOffset = true; - material.polygonOffsetUnit = 1; - material.polygonOffsetFactor = 1; - } - return material; - } } diff --git a/source/engine/viewer/viewer.js b/source/engine/viewer/viewer.js index 7d64115..8bd7c51 100644 --- a/source/engine/viewer/viewer.js +++ b/source/engine/viewer/viewer.js @@ -1,7 +1,7 @@ import { Coord3D, CoordDistance3D, SubCoord3D } from '../geometry/coord3d.js'; import { DegRad, Direction, IsEqual } from '../geometry/geometry.js'; import { ColorComponentToFloat } from '../model/color.js'; -import { ShadingType } from '../threejs/threeutils.js'; +import { CreateHighlightMaterials, ShadingType } from '../threejs/threeutils.js'; import { Camera, NavigationMode, ProjectionMode } from './camera.js'; import { GetDomElementInnerDimensions } from './domutils.js'; import { Navigation } from './navigation.js'; @@ -448,7 +448,7 @@ export class Viewer SetMeshesVisibility (isVisible) { - this.mainModel.EnumerateMeshes ((mesh) => { + this.mainModel.EnumerateMeshesAndLines ((mesh) => { let visible = isVisible (mesh.userData); if (mesh.visible !== visible) { mesh.visible = visible; @@ -465,22 +465,13 @@ export class Viewer SetMeshesHighlight (highlightColor, isHighlighted) { - function CreateHighlightMaterials (originalMaterials, highlightMaterial) - { - let highlightMaterials = []; - for (let i = 0; i < originalMaterials.length; i++) { - highlightMaterials.push (highlightMaterial); - } - return highlightMaterials; - } - - const highlightMaterial = this.CreateHighlightMaterial (highlightColor); - this.mainModel.EnumerateMeshes ((mesh) => { + let withPolygonOffset = this.mainModel.HasLinesOrEdges (); + this.mainModel.EnumerateMeshesAndLines ((mesh) => { let highlighted = isHighlighted (mesh.userData); if (highlighted) { if (mesh.userData.threeMaterials === null) { mesh.userData.threeMaterials = mesh.material; - mesh.material = CreateHighlightMaterials (mesh.material, highlightMaterial); + mesh.material = CreateHighlightMaterials (mesh.userData.threeMaterials, highlightColor, withPolygonOffset); } } else { if (mesh.userData.threeMaterials !== null) { @@ -493,12 +484,6 @@ export class Viewer this.Render (); } - CreateHighlightMaterial (highlightColor) - { - const showEdges = this.mainModel.edgeSettings.showEdges; - return this.shadingModel.CreateHighlightMaterial (highlightColor, showEdges); - } - GetMeshUserDataUnderMouse (mouseCoords) { let intersection = this.GetMeshIntersectionUnderMouse (mouseCoords); @@ -528,9 +513,9 @@ export class Viewer return this.mainModel.GetBoundingSphere (needToProcess); } - EnumerateMeshesUserData (enumerator) + EnumerateMeshesAndLinesUserData (enumerator) { - this.mainModel.EnumerateMeshes ((mesh) => { + this.mainModel.EnumerateMeshesAndLines ((mesh) => { enumerator (mesh.userData); }); } diff --git a/source/engine/viewer/viewermodel.js b/source/engine/viewer/viewermodel.js index 5ad8c39..15c8781 100644 --- a/source/engine/viewer/viewermodel.js +++ b/source/engine/viewer/viewermodel.js @@ -119,14 +119,24 @@ export class ViewerMainModel this.edgeModel = new ViewerModel (this.scene); this.edgeSettings = new EdgeSettings (false, new RGBColor (0, 0, 0), 1); + this.hasLines = false; + this.hasPolygonOffset = false; } SetMainObject (mainObject) { this.mainModel.SetRootObject (mainObject); + this.hasLines = false; + this.hasPolygonOffset = false; + + this.EnumerateLines ((line) => { + this.hasLines = true; + }); + if (this.edgeSettings.showEdges) { this.GenerateEdgeModel (); } + this.UpdatePolygonOffset (); } UpdateWorldMatrix () @@ -169,7 +179,6 @@ export class ViewerMainModel this.UpdateWorldMatrix (); this.EnumerateMeshes ((mesh) => { - SetThreeMeshPolygonOffset (mesh, true); let edges = new THREE.EdgesGeometry (mesh.geometry, this.edgeSettings.edgeThreshold); let line = new THREE.LineSegments (edges, new THREE.LineBasicMaterial ({ color: edgeColor @@ -179,13 +188,15 @@ export class ViewerMainModel line.visible = mesh.visible; this.edgeModel.AddObject (line); }); + + this.UpdatePolygonOffset (); } GetBoundingBox (needToProcess) { let hasMesh = false; let boundingBox = new THREE.Box3 (); - this.EnumerateMeshes ((mesh) => { + this.EnumerateMeshesAndLines ((mesh) => { if (needToProcess (mesh.userData)) { boundingBox.union (new THREE.Box3 ().setFromObject (mesh)); hasMesh = true; @@ -221,9 +232,7 @@ export class ViewerMainModel return; } - this.EnumerateMeshes ((mesh) => { - SetThreeMeshPolygonOffset (mesh, false); - }); + this.UpdatePolygonOffset (); this.edgeModel.Clear (); } @@ -232,7 +241,25 @@ export class ViewerMainModel this.mainModel.Traverse ((obj) => { if (obj.isMesh) { enumerator (obj); - } else if (obj.type === 'LineSegments') { + } + }); + } + + EnumerateLines (enumerator) + { + this.mainModel.Traverse ((obj) => { + if (obj.isLineSegments) { + enumerator (obj); + } + }); + } + + EnumerateMeshesAndLines (enumerator) + { + this.mainModel.Traverse ((obj) => { + if (obj.isMesh) { + enumerator (obj); + } else if (obj.isLineSegments) { enumerator (obj); } }); @@ -247,6 +274,22 @@ export class ViewerMainModel }); } + HasLinesOrEdges () + { + return this.hasLines || this.edgeSettings.showEdges; + } + + UpdatePolygonOffset () + { + let needPolygonOffset = this.HasLinesOrEdges (); + if (needPolygonOffset !== this.hasPolygonOffset) { + this.EnumerateMeshes ((mesh) => { + SetThreeMeshPolygonOffset (mesh, needPolygonOffset); + }); + this.hasPolygonOffset = needPolygonOffset; + } + } + GetMeshIntersectionUnderMouse (mouseCoords, camera, width, height) { if (this.mainModel.IsEmpty ()) { @@ -258,6 +301,8 @@ export class ViewerMainModel } let raycaster = new THREE.Raycaster (); + raycaster.params.Line.threshold = 0.1; + let mousePos = new THREE.Vector2 (); mousePos.x = (mouseCoords.x / width) * 2 - 1; mousePos.y = -(mouseCoords.y / height) * 2 + 1; @@ -265,7 +310,7 @@ export class ViewerMainModel let iSectObjects = raycaster.intersectObject (this.mainModel.GetRootObject (), true); for (let i = 0; i < iSectObjects.length; i++) { let iSectObject = iSectObjects[i]; - if (iSectObject.object.isMesh && iSectObject.object.visible) { + if ((iSectObject.object.isMesh || iSectObject.object.isLineSegments) && iSectObject.object.visible) { return iSectObject; } } diff --git a/source/website/website.js b/source/website/website.js index 1ad72cf..38016bc 100644 --- a/source/website/website.js +++ b/source/website/website.js @@ -19,7 +19,7 @@ import { ShowSnapshotDialog } from './snapshotdialog.js'; import { AddSvgIconElement, GetFilesFromDataTransfer, InstallTooltip, IsSmallWidth } from './utils.js'; import { ShowOpenUrlDialog } from './openurldialog.js'; import { ShowSharingDialog } from './sharingdialog.js'; -import { HasDefaultMaterial, ReplaceDefaultMaterialColor } from '../engine/model/modelutils.js'; +import { HasDefaultMaterial, ReplaceDefaultMaterialsColor } from '../engine/model/modelutils.js'; import { Direction } from '../engine/geometry/geometry.js'; import { CookieGetBoolVal, CookieSetBoolVal } from './cookiehandler.js'; import { MeasureTool } from './measuretool.js'; @@ -577,9 +577,9 @@ export class Website this.viewer.SetBackgroundColor (this.settings.backgroundColor); let modelLoader = this.modelLoaderUI.GetModelLoader (); - if (modelLoader.GetDefaultMaterial () !== null) { - ReplaceDefaultMaterialColor (this.model, this.settings.defaultColor); - modelLoader.ReplaceDefaultMaterialColor (this.settings.defaultColor); + if (modelLoader.GetDefaultMaterials () !== null) { + ReplaceDefaultMaterialsColor (this.model, this.settings.defaultColor); + modelLoader.ReplaceDefaultMaterialsColor (this.settings.defaultColor); } } @@ -814,9 +814,9 @@ export class Website onDefaultColorChanged : () => { this.settings.SaveToCookies (); let modelLoader = this.modelLoaderUI.GetModelLoader (); - if (modelLoader.GetDefaultMaterial () !== null) { - ReplaceDefaultMaterialColor (this.model, this.settings.defaultColor); - modelLoader.ReplaceDefaultMaterialColor (this.settings.defaultColor); + if (modelLoader.GetDefaultMaterials () !== null) { + ReplaceDefaultMaterialsColor (this.model, this.settings.defaultColor); + modelLoader.ReplaceDefaultMaterialsColor (this.settings.defaultColor); } this.viewer.Render (); }, @@ -839,7 +839,7 @@ export class Website function GetMeshUserDataArray (viewer, meshInstanceId) { let userDataArr = []; - viewer.EnumerateMeshesUserData ((meshUserData) => { + viewer.EnumerateMeshesAndLinesUserData ((meshUserData) => { if (meshUserData.originalMeshInstance.id.IsEqual (meshInstanceId)) { userDataArr.push (meshUserData); } @@ -850,7 +850,7 @@ export class Website function GetMeshesForMaterial (viewer, materialIndex) { let usedByMeshes = []; - viewer.EnumerateMeshesUserData ((meshUserData) => { + viewer.EnumerateMeshesAndLinesUserData ((meshUserData) => { if (materialIndex === null || meshUserData.originalMaterials.indexOf (materialIndex) !== -1) { usedByMeshes.push (meshUserData.originalMeshInstance); } diff --git a/test/testfiles/obj/cube_with_edges.mtl b/test/testfiles/obj/cube_with_edges.mtl new file mode 100644 index 0000000..edd0bee --- /dev/null +++ b/test/testfiles/obj/cube_with_edges.mtl @@ -0,0 +1,14 @@ +newmtl Red +Ka 0.000000 0.000000 0.000000 +Kd 0.800000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 + +newmtl Green +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.800000 0.000000 +Ks 0.500000 0.500000 0.500000 + +newmtl Blue +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.800000 +Ks 0.500000 0.500000 0.500000 diff --git a/test/testfiles/obj/cube_with_edges.obj b/test/testfiles/obj/cube_with_edges.obj new file mode 100644 index 0000000..bdfdb79 --- /dev/null +++ b/test/testfiles/obj/cube_with_edges.obj @@ -0,0 +1,44 @@ +# Cube with Materials + +mtllib cube_with_edges.mtl + +g Cube + +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 0.0 1.0 0.0 +v 0.0 0.0 1.0 +v 1.0 0.0 1.0 +v 1.0 1.0 1.0 +v 0.0 1.0 1.0 + +vn 1.0 0.0 0.0 +vn -1.0 0.0 0.0 +vn 0.0 1.0 0.0 +vn 0.0 -1.0 0.0 +vn 0.0 0.0 1.0 +vn 0.0 0.0 -1.0 + +vt 0.0 0.0 +vt 1.0 0.0 +vt 1.0 1.0 +vt 0.0 1.0 + +f 1/1/6 4/2/6 3/3/6 2/4/6 +f 2/1/1 3/2/1 7/3/1 6/4/1 +f 4/1/2 1/2/2 5/3/2 8/4/2 +f 5/1/5 6/2/5 7/3/5 8/4/5 + +l 1 4 2 3 6 7 5 8 + +usemtl Red +f 3/1/3 4/2/3 8/3/3 7/4/3 + +usemtl Blue +f 1/1/4 2/2/4 6/3/4 5/4/4 + +usemtl Green +l 3 4 4 8 8 7 7 3 +l 1 2 2 6 6 5 5 1 + diff --git a/test/testfiles/obj/lines_triangles_colors.obj b/test/testfiles/obj/lines_triangles_colors.obj index cd5acad..93a8ca3 100644 --- a/test/testfiles/obj/lines_triangles_colors.obj +++ b/test/testfiles/obj/lines_triangles_colors.obj @@ -2,23 +2,49 @@ mtllib lines_triangles_colors.mtl v 0.0 0.0 0.0 v 1.0 0.0 0.0 -v 1.0 1.0 0.0 -v 0.0 1.0 0.0 -v 0.0 0.0 1.0 v 1.0 0.0 1.0 -v 1.0 1.0 1.0 -v 0.0 1.0 1.0 -v 0.0 0.0 2.0 -v 1.0 0.0 2.0 -v 1.0 1.0 2.0 -v 0.0 1.0 2.0 +v 0.0 0.0 1.0 +v 0.5 1.0 0.5 + +g Mesh01 usemtl Red -l 1 2 -l 3 4 -f 9 10 11 -usemtl Green -l 5 6 6 7 7 8 8 5 -f 9 11 12 -usemtl Blue +f 1 2 3 l 1 5 -f 9 5 8 12 +usemtl Green +f 1 3 4 +l 3 5 + +v 2.0 0.0 0.0 +v 3.0 0.0 0.0 +v 3.0 0.0 1.0 +v 2.0 0.0 1.0 +v 2.5 1.0 0.5 + +g Mesh02 +usemtl Red +f 6 7 8 +l 6 10 +usemtl Green +f 6 8 9 +l 8 10 + +v 4.0 0.0 0.0 +v 5.0 0.0 0.0 +v 5.0 0.0 1.0 +v 4.0 0.0 1.0 + +g Mesh03 +usemtl Red +f 11 12 13 +usemtl Green +f 11 13 14 + +v 6.0 0.0 0.0 +v 7.0 0.0 1.0 +v 6.5 1.0 0.5 + +g Mesh04 +usemtl Red +l 15 17 +usemtl Green +l 16 17