/**
 * Utility code for working with three.js.
 */ import * as three from 'three' ; const extrudePolyline = require ( 'extrude-polyline' ) ; const Complex = require ( 'three-simplicial-complex' ) ( three ) ; import { Transform } from './coords' ; import { findClosestPoint , polylineDistance } from './geometry' ; import { Feature } from './utils' ; const OBJLoader = require ( 'three-obj-loader' ) ; OBJLoader ( three ) ; function shapeFromVertices ( vertices ) { const shape = new three . Shape ( ) ; shape . moveTo ( vertices [ 0 ] [ 0 ] , vertices [ 0 ] [ 1 ] ) ; for ( let i = 1 ; i < vertices . length ; i ++ ) { const [ x , y ] = vertices [ i ] ; shape . lineTo ( x , y ) ; } shape . closePath ( ) ; return shape ; } function addUVMappingToGeometry ( geometry ) { geometry . computeBoundingBox ( ) ; const { min , max } = geometry . boundingBox ; const offset = new three . Vector2 ( 0 - min . x , 0 - min . y ) ; const range = new three . Vector2 ( max . x - min . x , max . y - min . y ) ; const faces = geometry . faces ; geometry . faceVertexUvs [ 0 ] = [ ] ; for ( let i = 0 ; i < faces . length ; i ++ ) { const v1 = geometry . vertices [ faces [ i ] . a ] ; const v2 = geometry . vertices [ faces [ i ] . b ] ; const v3 = geometry . vertices [ faces [ i ] . c ] ; geometry . faceVertexUvs [ 0 ] . push ( [ new three . Vector2 ( ( v1 . x + offset . x ) / range . x , ( v1 . y + offset . y ) / range . y ) , new three . Vector2 ( ( v2 . x + offset . x ) / range . x , ( v2 . y + offset . y ) / range . y ) , new three . Vector2 ( ( v3 . x + offset . x ) / range . x , ( v3 . y + offset . y ) / range . y ) , ] ) ; } geometry . uvsNeedUpdate = true ; } function addUVMappingToExtrudedGeometry ( geometry , vertices , sideScale , topBottomScale ) { geometry . computeBoundingBox ( ) ; const { min } = geometry . boundingBox ; const offset = new three . Vector3 ( 0 - min . x , 0 - min . y , 0 - min . z ) ; const faces = geometry . faces ; geometry . faceVertexUvs [ 0 ] = [ ] ; for ( const face of faces ) { const v1 = geometry . vertices [ face . a ] ; const v2 = geometry . vertices [ face . b ] ; const v3 = geometry . vertices [ face . c ] ; if ( face . normal . z === 0 ) { const d1 = polylineDistance ( vertices , [ v1 . x , v1 . y ] , true ) ; const d2 = polylineDistance ( vertices , [ v2 . x , v2 . y ] , true ) ; const d3 = polylineDistance ( vertices , [ v3 . x , v3 . y ] , true ) ; geometry . faceVertexUvs [ 0 ] . push ( [ new three . Vector2 ( d1 . dLine * sideScale , v1 . z * sideScale ) , new three . Vector2 ( d2 . dLine * sideScale , v2 . z * sideScale ) , new three . Vector2 ( d3 . dLine * sideScale , v3 . z * sideScale ) , ] ) ; } else { geometry . faceVertexUvs [ 0 ] . push ( [ new three . Vector2 ( ( v1 . x + offset . x ) * topBottomScale , ( v1 . y + offset . y ) * topBottomScale ) , new three . Vector2 ( ( v2 . x + offset . x ) * topBottomScale , ( v2 . y + offset . y ) * topBottomScale ) , new three . Vector2 ( ( v3 . x + offset . x ) * topBottomScale , ( v3 . y + offset . y ) * topBottomScale ) , ] ) ; } } geometry . uvsNeedUpdate = true ; } function addUVMappingToGeometryWithPolyline ( geometry , points , width , uScaleFactor , transform ) { const { faces } = geometry ; for ( let i = 0 ; i < faces . length ; i ++ ) { const v1 = geometry . vertices [ faces [ i ] . a ] ; const v2 = geometry . vertices [ faces [ i ] . b ] ; const v3 = geometry . vertices [ faces [ i ] . c ] ; const d1 = polylineDistance ( points , [ v1 . x , transform . bottom - v1 . z ] ) ; const d2 = polylineDistance ( points , [ v2 . x , transform . bottom - v2 . z ] ) ; const d3 = polylineDistance ( points , [ v3 . x , transform . bottom - v3 . z ] ) ; geometry . faceVertexUvs [ 0 ] . push ( [ new three . Vector2 ( d1 . dLine * uScaleFactor , d1 . dPerp / width + 0.5 ) , new three . Vector2 ( d2 . dLine * uScaleFactor , d2 . dPerp / width + 0.5 ) , new three . Vector2 ( d3 . dLine * uScaleFactor , d3 . dPerp / width + 0.5 ) , ] ) ; } geometry . uvsNeedUpdate = true ; } /** Return a flat mesh from a polygon's vertices. The polygon will have y=0. */ export function flatMeshFromVertices ( vertices , material ) { const shape = shapeFromVertices ( vertices ) ; const geometry = new three . ShapeGeometry ( shape ) ; addUVMappingToGeometry ( geometry ) ; const mesh = new three . Mesh ( geometry , material ) ; mesh . material . side = three . DoubleSide ; mesh . rotation . set ( Math . PI / 2 , 0 , 0 ) ; return mesh ; } export function flatRectMesh ( { top , left , right , bottom } , material ) { return flatMeshFromVertices ( [ [ left , top ] , [ right , top ] , [ right , bottom ] , [ left , bottom ] ] , material ) ; } /** Creates a shape with y=0 and extrudes it up to y=height */ export function extrudedMeshFromVertices ( vertices , height , topBottomUVScale , sideUVScale , materials ) { const shape = shapeFromVertices ( vertices ) ; const geometry = new three . ExtrudeGeometry ( shape , { amount : height , bevelEnabled : false , } ) ; addUVMappingToExtrudedGeometry ( geometry , vertices , topBottomUVScale , sideUVScale ) ; const mesh = new three . Mesh ( geometry , materials as any ) ; mesh . rotation . set ( Math . PI / 2 , 0 , 0 ) ; mesh . position . setY ( height ) ; return mesh ; } export interface LineParams { width : number ; uScaleFactor ? : number ; } /** Create an object from an array of 2D or 3D points in SUMO coordinates. */ export function lineString ( points , transform , params ) { const simplicialComplex = extrudePolyline ( { thickness : params . width , cap : 'square' , join : 'bevel' , miterLimit : 10 , } ) . build ( points ) ; simplicialComplex . positions = simplicialComplex . positions . map ( ( position ) => { const xyz = transform . xyToXyz ( position ) ; const closestPoint = findClosestPoint ( xyz , points ) ; return [ xyz [ 0 ] , closestPoint && closestPoint [ 2 ] ? closestPoint [ 2 ] : 0 , xyz [ 2 ] ] ; } ) ; const geometry = Complex ( simplicialComplex ) ; geometry . computeFaceNormals ( ) ; let anyFlipped = false ; for ( const face of geometry . faces ) { if ( face . normal . y > 0 ) { const { a , c } = face ; face . a = c ; face . c = a ; anyFlipped = true ; } } if ( anyFlipped ) { geometry . computeFaceNormals ( ) ; } addUVMappingToGeometryWithPolyline ( geometry , points , params . width , params . uScaleFactor || 1 , transform ) ; return geometry ; } function polygonToShape ( coordinates ) { const outer = shapeFromVertices ( coordinates [ 0 ] ) ; for ( let i = 1 ; i < coordinates . length ; i ++ ) { const inner = shapeFromVertices ( coordinates [ i ] ) ; outer . holes . push ( inner ) ; } return outer ; } /** Convert a GeoJSON feature toa  three.js Geometry */ export function featureToGeometry ( feature ) { const geometry = feature . geometry ; if ( geometry . type === 'MultiPolygon' ) { const coordinates = geometry . coordinates ; const shapes = coordinates . map ( polygonToShape ) ; const geom = new three . ShapeGeometry ( shapes ) ; addUVMappingToGeometry ( geom ) ; return geom ; } else if ( geometry . type === 'Polygon' ) { const coordinates = geometry . coordinates ; const shape = polygonToShape ( coordinates ) ; const geom = new three . ShapeGeometry ( shape ) ; addUVMappingToGeometry ( geom ) ; return geom ; } else { throw new Error ( `Geometry type ${ geometry . type } not supported.` ) ; } } const OBJ_LOADER = new three . OBJLoader ( ) ; /** Load an OBJ file, returning a Promise for it. Optionally adds a material. */ export function loadOBJFile ( url , material ? ) { return new Promise < three . Object3D > ( ( resolve , reject ) => { OBJ_LOADER . load ( url , obj => { if ( material ) { setMaterial ( obj , material ) ; } resolve ( obj ) ; } , ( ) => { } , reject ) ; } ) ; } /** Set a material on all meshes in an object. */ export function setMaterial ( obj , material ) { obj . traverse ( child => { if ( child instanceof three . Mesh ) { child . material = material ; } } ) ; } /** Merge the mesh into the geometry, copying its userData onto the new faces. */ export function mergeMeshWithUserData ( geometry , mesh ) { const numFaces = geometry . faces . length ; geometry . mergeMesh ( mesh ) ; for ( let i = numFaces ; i < geometry . faces . length ; i ++ ) { ( geometry . faces [ i ] as any ) . userData = mesh . userData ; } return geometry ; }