csg.js 362 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702
  1. /*
  2. ## IMPORTANT NOTE --- IMPORTANT
  3. The master for this file is located at:
  4. https://github.com/joostn/openjscad/tree/gh-pages
  5. That is the gh-pages branch of the joostn/openjscad project
  6. If contributing from openjscad.org, please do NOT edit this local file but make pull requests against
  7. above joostn/gh-pages branch.
  8. ## IMPORTANT NOTE --- IMPORTANT NOTE
  9. ## License
  10. Copyright (c) 2014 bebbi (elghatta@gmail.com)
  11. Copyright (c) 2013 Eduard Bespalov (edwbes@gmail.com)
  12. Copyright (c) 2012 Joost Nieuwenhuijse (joost@newhouse.nl)
  13. Copyright (c) 2011 Evan Wallace (http://evanw.github.com/csg.js/)
  14. Copyright (c) 2012 Alexandre Girard (https://github.com/alx)
  15. All code released under MIT license
  16. ## Overview
  17. For an overview of the CSG process see the original csg.js code:
  18. http://evanw.github.com/csg.js/
  19. CSG operations through BSP trees suffer from one problem: heavy fragmentation
  20. of polygons. If two CSG solids of n polygons are unified, the resulting solid may have
  21. in the order of n*n polygons, because each polygon is split by the planes of all other
  22. polygons. After a few operations the number of polygons explodes.
  23. This version of CSG.js solves the problem in 3 ways:
  24. 1. Every polygon split is recorded in a tree (CSG.PolygonTreeNode). This is a separate
  25. tree, not to be confused with the CSG tree. If a polygon is split into two parts but in
  26. the end both fragments have not been discarded by the CSG operation, we can retrieve
  27. the original unsplit polygon from the tree, instead of the two fragments.
  28. This does not completely solve the issue though: if a polygon is split multiple times
  29. the number of fragments depends on the order of subsequent splits, and we might still
  30. end up with unncessary splits:
  31. Suppose a polygon is first split into A and B, and then into A1, B1, A2, B2. Suppose B2 is
  32. discarded. We will end up with 2 polygons: A and B1. Depending on the actual split boundaries
  33. we could still have joined A and B1 into one polygon. Therefore a second approach is used as well:
  34. 2. After CSG operations all coplanar polygon fragments are joined by a retesselating
  35. operation. See CSG.reTesselated(). Retesselation is done through a
  36. linear sweep over the polygon surface. The sweep line passes over the y coordinates
  37. of all vertices in the polygon. Polygons are split at each sweep line, and the fragments
  38. are joined horizontally and vertically into larger polygons (making sure that we
  39. will end up with convex polygons).
  40. This still doesn't solve the problem completely: due to floating point imprecisions
  41. we may end up with small gaps between polygons, and polygons may not be exactly coplanar
  42. anymore, and as a result the retesselation algorithm may fail to join those polygons.
  43. Therefore:
  44. 3. A canonicalization algorithm is implemented: it looks for vertices that have
  45. approximately the same coordinates (with a certain tolerance, say 1e-5) and replaces
  46. them with the same vertex. If polygons share a vertex they will actually point to the
  47. same CSG.Vertex instance. The same is done for polygon planes. See CSG.canonicalized().
  48. Performance improvements to the original CSG.js:
  49. Replaced the flip() and invert() methods by flipped() and inverted() which don't
  50. modify the source object. This allows to get rid of all clone() calls, so that
  51. multiple polygons can refer to the same CSG.Plane instance etc.
  52. The original union() used an extra invert(), clipTo(), invert() sequence just to remove the
  53. coplanar front faces from b; this is now combined in a single b.clipTo(a, true) call.
  54. Detection whether a polygon is in front or in back of a plane: for each polygon
  55. we are caching the coordinates of the bounding sphere. If the bounding sphere is
  56. in front or in back of the plane we don't have to check the individual vertices
  57. anymore.
  58. Other additions to the original CSG.js:
  59. CSG.Vector class has been renamed into CSG.Vector3D
  60. Classes for 3D lines, 2D vectors, 2D lines, and methods to find the intersection of
  61. a line and a plane etc.
  62. Transformations: CSG.transform(), CSG.tr(), CSG.rotate(), CSG.scale()
  63. Expanding or contracting a solid: CSG.expand() and CSG.contract(). Creates nice
  64. smooth corners.
  65. The vertex normal has been removed since it complicates retesselation. It's not needed
  66. for solid CAD anyway.
  67. */
  68. (function(module) {
  69. var _CSGDEBUG = false;
  70. function fnNumberSort(a, b) {
  71. return a - b;
  72. }
  73. // # class CSG
  74. // Holds a binary space partition tree representing a 3D solid. Two solids can
  75. // be combined using the `union()`, `subtract()`, and `intersect()` methods.
  76. var CSG = function() {
  77. this.polygons = [];
  78. this.properties = new CSG.Properties();
  79. this.isCanonicalized = true;
  80. this.isRetesselated = true;
  81. };
  82. CSG.defaultResolution2D = 32;
  83. CSG.defaultResolution3D = 12;
  84. // Construct a CSG solid from a list of `CSG.Polygon` instances.
  85. CSG.fromPolygons = function(polygons) {
  86. var csg = new CSG();
  87. csg.polygons = polygons;
  88. csg.isCanonicalized = false;
  89. csg.isRetesselated = false;
  90. return csg;
  91. };
  92. // Construct a CSG solid from generated slices.
  93. // Look at CSG.Polygon.prototype.solidFromSlices for details
  94. CSG.fromSlices = function(options) {
  95. return (new CSG.Polygon.createFromPoints([
  96. [0, 0, 0],
  97. [1, 0, 0],
  98. [1, 1, 0],
  99. [0, 1, 0]
  100. ])).solidFromSlices(options);
  101. };
  102. // create from an untyped object with identical property names:
  103. CSG.fromObject = function(obj) {
  104. var polygons = obj.polygons.map(function(p) {
  105. return CSG.Polygon.fromObject(p);
  106. });
  107. var csg = CSG.fromPolygons(polygons);
  108. csg.isCanonicalized = obj.isCanonicalized;
  109. csg.isRetesselated = obj.isRetesselated;
  110. return csg;
  111. };
  112. CSG.uniqBy = function(a, key) {
  113. var seen = {};
  114. return a.filter(function(item) {
  115. var k = key(item);
  116. return seen.hasOwnProperty(k) ? false : (seen[k] = true);
  117. })
  118. };
  119. // Reconstruct a CSG from the output of toCompactBinary()
  120. CSG.fromCompactBinary = function(bin) {
  121. if (bin['class'] != "CSG") throw new Error("Not a CSG");
  122. var planes = [],
  123. planeData = bin.planeData,
  124. numplanes = planeData.length / 4,
  125. arrayindex = 0,
  126. x, y, z, w, normal, plane;
  127. for (var planeindex = 0; planeindex < numplanes; planeindex++) {
  128. x = planeData[arrayindex++];
  129. y = planeData[arrayindex++];
  130. z = planeData[arrayindex++];
  131. w = planeData[arrayindex++];
  132. normal = CSG.Vector3D.Create(x, y, z);
  133. plane = new CSG.Plane(normal, w);
  134. planes.push(plane);
  135. }
  136. var vertices = [],
  137. vertexData = bin.vertexData,
  138. numvertices = vertexData.length / 3,
  139. pos, vertex;
  140. arrayindex = 0;
  141. for (var vertexindex = 0; vertexindex < numvertices; vertexindex++) {
  142. x = vertexData[arrayindex++];
  143. y = vertexData[arrayindex++];
  144. z = vertexData[arrayindex++];
  145. pos = CSG.Vector3D.Create(x, y, z);
  146. vertex = new CSG.Vertex(pos);
  147. vertices.push(vertex);
  148. }
  149. var shareds = bin.shared.map(function(shared) {
  150. return CSG.Polygon.Shared.fromObject(shared);
  151. });
  152. var polygons = [],
  153. numpolygons = bin.numPolygons,
  154. numVerticesPerPolygon = bin.numVerticesPerPolygon,
  155. polygonVertices = bin.polygonVertices,
  156. polygonPlaneIndexes = bin.polygonPlaneIndexes,
  157. polygonSharedIndexes = bin.polygonSharedIndexes,
  158. numpolygonvertices, polygonvertices, shared, polygon; //already defined plane,
  159. arrayindex = 0;
  160. for (var polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
  161. numpolygonvertices = numVerticesPerPolygon[polygonindex];
  162. polygonvertices = [];
  163. for (var i = 0; i < numpolygonvertices; i++) {
  164. polygonvertices.push(vertices[polygonVertices[arrayindex++]]);
  165. }
  166. plane = planes[polygonPlaneIndexes[polygonindex]];
  167. shared = shareds[polygonSharedIndexes[polygonindex]];
  168. polygon = new CSG.Polygon(polygonvertices, shared, plane);
  169. polygons.push(polygon);
  170. }
  171. var csg = CSG.fromPolygons(polygons);
  172. csg.isCanonicalized = true;
  173. csg.isRetesselated = true;
  174. return csg;
  175. };
  176. CSG.prototype = {
  177. toPolygons: function() {
  178. return this.polygons;
  179. },
  180. // Return a new CSG solid representing space in either this solid or in the
  181. // solid `csg`. Neither this solid nor the solid `csg` are modified.
  182. //
  183. // A.union(B)
  184. //
  185. // +-------+ +-------+
  186. // | | | |
  187. // | A | | |
  188. // | +--+----+ = | +----+
  189. // +----+--+ | +----+ |
  190. // | B | | |
  191. // | | | |
  192. // +-------+ +-------+
  193. //
  194. union: function(csg) {
  195. var csgs;
  196. if (csg instanceof Array) {
  197. csgs = csg.slice(0);
  198. csgs.push(this);
  199. } else {
  200. csgs = [this, csg];
  201. }
  202. // combine csg pairs in a way that forms a balanced binary tree pattern
  203. for (var i = 1; i < csgs.length; i += 2) {
  204. csgs.push(csgs[i-1].unionSub(csgs[i]));
  205. }
  206. return csgs[i - 1].reTesselated().canonicalized();
  207. },
  208. unionSub: function(csg, retesselate, canonicalize) {
  209. if (!this.mayOverlap(csg)) {
  210. return this.unionForNonIntersecting(csg);
  211. } else {
  212. var a = new CSG.Tree(this.polygons);
  213. var b = new CSG.Tree(csg.polygons);
  214. a.clipTo(b, false);
  215. // b.clipTo(a, true); // ERROR: this doesn't work
  216. b.clipTo(a);
  217. b.invert();
  218. b.clipTo(a);
  219. b.invert();
  220. var newpolygons = a.allPolygons().concat(b.allPolygons());
  221. var result = CSG.fromPolygons(newpolygons);
  222. result.properties = this.properties._merge(csg.properties);
  223. if (retesselate) result = result.reTesselated();
  224. if (canonicalize) result = result.canonicalized();
  225. return result;
  226. }
  227. },
  228. // Like union, but when we know that the two solids are not intersecting
  229. // Do not use if you are not completely sure that the solids do not intersect!
  230. unionForNonIntersecting: function(csg) {
  231. var newpolygons = this.polygons.concat(csg.polygons);
  232. var result = CSG.fromPolygons(newpolygons);
  233. result.properties = this.properties._merge(csg.properties);
  234. result.isCanonicalized = this.isCanonicalized && csg.isCanonicalized;
  235. result.isRetesselated = this.isRetesselated && csg.isRetesselated;
  236. return result;
  237. },
  238. // Return a new CSG solid representing space in this solid but not in the
  239. // solid `csg`. Neither this solid nor the solid `csg` are modified.
  240. //
  241. // A.subtract(B)
  242. //
  243. // +-------+ +-------+
  244. // | | | |
  245. // | A | | |
  246. // | +--+----+ = | +--+
  247. // +----+--+ | +----+
  248. // | B |
  249. // | |
  250. // +-------+
  251. //
  252. subtract: function(csg) {
  253. var csgs;
  254. if (csg instanceof Array) {
  255. csgs = csg;
  256. } else {
  257. csgs = [csg];
  258. }
  259. var result = this;
  260. for (var i = 0; i < csgs.length; i++) {
  261. var islast = (i == (csgs.length - 1));
  262. result = result.subtractSub(csgs[i], islast, islast);
  263. }
  264. return result;
  265. },
  266. subtractSub: function(csg, retesselate, canonicalize) {
  267. var a = new CSG.Tree(this.polygons);
  268. var b = new CSG.Tree(csg.polygons);
  269. a.invert();
  270. a.clipTo(b);
  271. b.clipTo(a, true);
  272. a.addPolygons(b.allPolygons());
  273. a.invert();
  274. var result = CSG.fromPolygons(a.allPolygons());
  275. result.properties = this.properties._merge(csg.properties);
  276. if (retesselate) result = result.reTesselated();
  277. if (canonicalize) result = result.canonicalized();
  278. return result;
  279. },
  280. // Return a new CSG solid representing space both this solid and in the
  281. // solid `csg`. Neither this solid nor the solid `csg` are modified.
  282. //
  283. // A.intersect(B)
  284. //
  285. // +-------+
  286. // | |
  287. // | A |
  288. // | +--+----+ = +--+
  289. // +----+--+ | +--+
  290. // | B |
  291. // | |
  292. // +-------+
  293. //
  294. intersect: function(csg) {
  295. var csgs;
  296. if (csg instanceof Array) {
  297. csgs = csg;
  298. } else {
  299. csgs = [csg];
  300. }
  301. var result = this;
  302. for (var i = 0; i < csgs.length; i++) {
  303. var islast = (i == (csgs.length - 1));
  304. result = result.intersectSub(csgs[i], islast, islast);
  305. }
  306. return result;
  307. },
  308. intersectSub: function(csg, retesselate, canonicalize) {
  309. var a = new CSG.Tree(this.polygons);
  310. var b = new CSG.Tree(csg.polygons);
  311. a.invert();
  312. b.clipTo(a);
  313. b.invert();
  314. a.clipTo(b);
  315. b.clipTo(a);
  316. a.addPolygons(b.allPolygons());
  317. a.invert();
  318. var result = CSG.fromPolygons(a.allPolygons());
  319. result.properties = this.properties._merge(csg.properties);
  320. if (retesselate) result = result.reTesselated();
  321. if (canonicalize) result = result.canonicalized();
  322. return result;
  323. },
  324. // hull3d
  325. hull: function(csg) {
  326. var getPoints = function(csgs) {
  327. if ( !Array.isArray(csgs) ) {
  328. csgs = [csgs];
  329. }
  330. // make a list of all unique vertices
  331. var vertex_array = [];
  332. for (var i = 0; i < csgs.length; i++) {
  333. for (var j = 0; j < csgs[i].polygons.length; j++) {
  334. for (var k = 0; k < csgs[i].polygons[j].vertices.length; k++) {
  335. vertex_array.push([csgs[i].polygons[j].vertices[k].pos._x,
  336. csgs[i].polygons[j].vertices[k].pos._y,
  337. csgs[i].polygons[j].vertices[k].pos._z ])
  338. }
  339. }
  340. }
  341. var result = CSG.uniqBy(vertex_array, JSON.stringify);
  342. // console.log("uniq points:", result);
  343. var points = [];
  344. for (var i = 0; i < result.length; i++) {
  345. points.push(CSG.Vector3D.Create(result[i][0], result[i][1], result[i][2]));
  346. }
  347. return points;
  348. }
  349. var getColor = function(csgs) {
  350. // get the color from the first polygon of the first shape.
  351. if (csgs[0].polygons[0].shared)
  352. return csgs[0].polygons[0].shared.color;
  353. else return null;
  354. }
  355. var top_guy = this;
  356. var other_csgs = csg;
  357. var csgs = [];
  358. csgs.push(top_guy);
  359. for(var i = 0; i < other_csgs.length; i++) {
  360. csgs.push(other_csgs[i]);
  361. }
  362. for(var i=0; i<csgs.length; i++) { // check for mixing 2d and 3d
  363. var blah = csgs[i];
  364. if(!(blah instanceof CSG)) {
  365. // console.log("found a CAG in the CSG hull");
  366. throw("ERROR: don't mix 2D and 3D shapes in hull");
  367. //return new CSG();
  368. }
  369. }
  370. var points = getPoints(csgs);
  371. // console.log("points for hull are:",points);
  372. var color = getColor(csgs);
  373. var qhull = new CSG.quickHull3D();
  374. var faces = qhull.build(points);
  375. // console.log(faces);
  376. var polygons = [];
  377. for (var i = 0; i < faces.length; i++) {
  378. // each index is a face. I need to get the points and make an array of them
  379. // to send to CSG.Polygon.createFromPoints
  380. var pp = [];
  381. for (var j = 0; j < faces[i].length; j++) {
  382. // each of these should be an integer index into the original points variable.
  383. pp.push(points[faces[i][j]]);
  384. }
  385. // console.log("points for polygons");
  386. // console.log(pp);
  387. // var thispoly = new CSG.Polygon.createFromPoints(pp).sC(color);
  388. var np = new CSG.Polygon.createFromPoints(pp);
  389. if (color)
  390. np.sC(color);
  391. polygons.push(np);
  392. }
  393. // console.log("polygons");
  394. // console.log(polygons);
  395. // I don't think I need to retesselate or canonicalize this CSG.
  396. var hulledShape = CSG.fromPolygons(polygons);
  397. hulledShape.isCanonicalized = true;
  398. hulledShape.isRetesselated = true;
  399. return CSG.fromPolygons(polygons);
  400. },
  401. // end hull3d
  402. // Return a new CSG solid with solid and empty space switched. This solid is
  403. // not modified.
  404. invert: function() {
  405. var flippedpolygons = this.polygons.map(function(p) {
  406. return p.flipped();
  407. });
  408. return CSG.fromPolygons(flippedpolygons);
  409. // TODO: flip properties?
  410. },
  411. // Affine transformation of CSG object. Returns a new CSG object
  412. transform1: function(matrix4x4) {
  413. var newpolygons = this.polygons.map(function(p) {
  414. return p.transform(matrix4x4);
  415. });
  416. var result = CSG.fromPolygons(newpolygons);
  417. result.properties = this.properties._transform(matrix4x4);
  418. result.isRetesselated = this.isRetesselated;
  419. return result;
  420. },
  421. transform: function(matrix4x4) {
  422. var ismirror = matrix4x4.isMirroring();
  423. var transformedvertices = {};
  424. var transformedplanes = {};
  425. var newpolygons = this.polygons.map(function(p) {
  426. var newplane;
  427. var plane = p.plane;
  428. var planetag = plane.getTag();
  429. if (planetag in transformedplanes) {
  430. newplane = transformedplanes[planetag];
  431. } else {
  432. newplane = plane.transform(matrix4x4);
  433. transformedplanes[planetag] = newplane;
  434. }
  435. var newvertices = p.vertices.map(function(v) {
  436. var newvertex;
  437. var vertextag = v.getTag();
  438. if (vertextag in transformedvertices) {
  439. newvertex = transformedvertices[vertextag];
  440. } else {
  441. newvertex = v.transform(matrix4x4);
  442. transformedvertices[vertextag] = newvertex;
  443. }
  444. return newvertex;
  445. });
  446. if (ismirror) newvertices.reverse();
  447. return new CSG.Polygon(newvertices, p.shared, newplane);
  448. });
  449. var result = CSG.fromPolygons(newpolygons);
  450. result.properties = this.properties._transform(matrix4x4);
  451. result.isRetesselated = this.isRetesselated;
  452. result.isCanonicalized = this.isCanonicalized;
  453. return result;
  454. },
  455. taper: function(vector, factor) {
  456. // tapering to 0 leads to bad shapes. Negative numbers are right out.
  457. if (factor <= 0)
  458. factor = 0.0001;
  459. var bounds = this.getBounds();
  460. var max_distance = 0;
  461. var start_pos = 0;
  462. // bounds[0] contains maximum coordinates, bounds[1] is a vector with min.
  463. // calculate the total distance of the model along the relevant axis,
  464. // and the lowest value along that axis.
  465. if (vector[0]) {
  466. max_distance = bounds[1].x - bounds[0].x;
  467. start_pos = bounds[0].x
  468. }
  469. else if (vector[1]) {
  470. max_distance = bounds[1].y - bounds[0].y;
  471. start_pos = bounds[0].y
  472. }
  473. else if (vector[2]) {
  474. max_distance = bounds[1].z - bounds[0].z;
  475. start_pos = bounds[0].z
  476. }
  477. // I have to triangulate the polygons to prevent non-planar faces.
  478. var triangPolys = [];
  479. var nVert = [];
  480. var nPoly = [];
  481. var color = null;
  482. // keep triangle polygons, otherwise triangulate. Make sure to save color information.
  483. for (var i = 0; i < this.polygons.length; i++) {
  484. if (this.polygons[i].shared)
  485. color = this.polygons[i].shared.color;
  486. else color = null;
  487. if (this.polygons[i].vertices.length > 3) {
  488. // console.log("polygon of vertLength:", this.polygons[i].vertices.length);
  489. var point = this.polygons[i].vertices;
  490. var point0 = [point[0].pos.x, point[0].pos.y,point[0].pos.z];
  491. // console.log(point);
  492. nVert = [];
  493. nPoly = [];
  494. // if verts of a polygon are numbered 0,1,2,...,length-1
  495. // my triangles have verts [0,1,2], [0,2,3], ..., [0,length-2,length-1]
  496. for (var j = 1; j < this.polygons[i].vertices.length - 1; j++) {
  497. nVert = [];
  498. nPoly = [];
  499. nVert[0] = point0;
  500. nVert[1] = [point[j].pos.x, point[j].pos.y, point[j].pos.z];
  501. nVert[2] = [point[j + 1].pos.x, point[j + 1].pos.y, point[j + 1].pos.z];
  502. nPoly = new CSG.Polygon.createFromPoints(nVert);
  503. if (color)
  504. nPoly.sC(color);
  505. triangPolys.push(nPoly);
  506. }
  507. }
  508. else triangPolys.push(this.polygons[i]);
  509. }
  510. // now go through all the triangles and scale the vertices.
  511. var newPolys = [];
  512. var newVert = [];
  513. for (var i = 0; i < triangPolys.length; i++) {
  514. if (triangPolys[i].shared)
  515. color = triangPolys[i].shared.color;
  516. else color = null;
  517. newVert = [];
  518. for (var j = 0; j < triangPolys[i].vertices.length; j++) {
  519. if (triangPolys[i].vertices.length > 3)
  520. console.log("bad facet - more than 3 vertices!");
  521. var point = triangPolys[i].vertices[j].pos;
  522. if (vector[0]) {
  523. // Taper along the X-axis by "factor".
  524. var x = point.x;
  525. var y = point.y * (1 + (factor - 1) * (point.x - start_pos)/max_distance);
  526. var z = point.z * (1 + (factor - 1) * (point.x - start_pos)/max_distance);
  527. }
  528. else if (vector[1]) {
  529. // Taper along the Y-axis by "factor".
  530. var y = point.y;
  531. var x = point.x * (1 + (factor - 1) * (point.y - start_pos)/max_distance);
  532. var z = point.z * (1 + (factor - 1) * (point.y - start_pos)/max_distance);
  533. }
  534. else {
  535. // Taper along the Z-axis by "factor".
  536. var z = point.z;
  537. var y = point.y * (1 + (factor - 1) * (point.z - start_pos)/max_distance);
  538. var x = point.x * (1 + (factor - 1) * (point.z - start_pos)/max_distance);
  539. }
  540. newVert[j] = [x,y,z];
  541. }
  542. newPolys[i] = new CSG.Polygon.createFromPoints(newVert);
  543. if (color)
  544. newPolys[i].sC(color);
  545. }
  546. return CSG.fromPolygons(newPolys);
  547. },
  548. toString: function() {
  549. var result = "CSG solid:\n";
  550. this.polygons.map(function(p) {
  551. result += p.toString();
  552. });
  553. return result;
  554. },
  555. // Expand the solid
  556. // resolution: number of points per 360 degree for the rounded corners
  557. expand: function(radius, resolution) {
  558. var result = this.expandedShell(radius, resolution, true);
  559. result = result.reTesselated();
  560. result.properties = this.properties; // keep original properties
  561. return result;
  562. },
  563. // Contract the solid
  564. // resolution: number of points per 360 degree for the rounded corners
  565. contract: function(radius, resolution) {
  566. var expandedshell = this.expandedShell(radius, resolution, false);
  567. var result = this.subtract(expandedshell);
  568. result = result.reTesselated();
  569. result.properties = this.properties; // keep original properties
  570. return result;
  571. },
  572. // cut the solid at a plane, and stretch the cross-section found along plane normal
  573. stretchAtPlane: function(normal, point, length) {
  574. var plane = CSG.Plane.fromNormalAndPoint(normal, point);
  575. var onb = new CSG.OrthoNormalBasis(plane);
  576. var crosssect = this.sectionCut(onb);
  577. var midpiece = crosssect.extrudeInOrthonormalBasis(onb, length);
  578. var piece1 = this.cutByPlane(plane);
  579. var piece2 = this.cutByPlane(plane.flipped());
  580. var result = piece1.union([midpiece, piece2.tr(plane.normal.times(length))]);
  581. return result;
  582. },
  583. // Create the expanded shell of the solid:
  584. // All faces are extruded to get a thickness of 2*radius
  585. // Cylinders are constructed around every side
  586. // Spheres are placed on every vertex
  587. // unionWithThis: if true, the resulting solid will be united with 'this' solid;
  588. // the result is a true expansion of the solid
  589. // If false, returns only the shell
  590. expandedShell: function(radius, resolution, unionWithThis) {
  591. var csg = this.reTesselated();
  592. var result;
  593. if (unionWithThis) {
  594. result = csg;
  595. } else {
  596. result = new CSG();
  597. }
  598. // first extrude all polygons:
  599. csg.polygons.map(function(polygon) {
  600. var extrudevector = polygon.plane.normal.unit().times(2 * radius);
  601. var trdpolygon = polygon.tr(extrudevector.times(-0.5));
  602. var extrudedface = trdpolygon.extrude(extrudevector);
  603. result = result.unionSub(extrudedface, false, false);
  604. });
  605. // Make a list of all unique vertex pairs (i.e. all sides of the solid)
  606. // For each vertex pair we collect the following:
  607. // v1: first coordinate
  608. // v2: second coordinate
  609. // planenormals: array of normal vectors of all planes touching this side
  610. var vertexpairs = {}; // map of 'vertex pair tag' to {v1, v2, planenormals}
  611. csg.polygons.map(function(polygon) {
  612. var numvertices = polygon.vertices.length;
  613. var prevvertex = polygon.vertices[numvertices - 1];
  614. var prevvertextag = prevvertex.getTag();
  615. for (var i = 0; i < numvertices; i++) {
  616. var vertex = polygon.vertices[i];
  617. var vertextag = vertex.getTag();
  618. var vertextagpair;
  619. if (vertextag < prevvertextag) {
  620. vertextagpair = vertextag + "-" + prevvertextag;
  621. } else {
  622. vertextagpair = prevvertextag + "-" + vertextag;
  623. }
  624. var obj;
  625. if (vertextagpair in vertexpairs) {
  626. obj = vertexpairs[vertextagpair];
  627. } else {
  628. obj = {
  629. v1: prevvertex,
  630. v2: vertex,
  631. planenormals: []
  632. };
  633. vertexpairs[vertextagpair] = obj;
  634. }
  635. obj.planenormals.push(polygon.plane.normal);
  636. prevvertextag = vertextag;
  637. prevvertex = vertex;
  638. }
  639. });
  640. // now construct a cylinder on every side
  641. // The cylinder is always an approximation of a true cylinder: it will have <resolution> polygons
  642. // around the sides. We will make sure though that the cylinder will have an edge at every
  643. // face that touches this side. This ensures that we will get a smooth fill even
  644. // if two edges are at, say, 10 degrees and the resolution is low.
  645. // Note: the result is not retesselated yet but it really should be!
  646. for (var vertextagpair in vertexpairs) {
  647. var vertexpair = vertexpairs[vertextagpair],
  648. startpoint = vertexpair.v1.pos,
  649. endpoint = vertexpair.v2.pos,
  650. // our x,y and z vectors:
  651. zbase = endpoint.minus(startpoint).unit(),
  652. xbase = vertexpair.planenormals[0].unit(),
  653. ybase = xbase.cross(zbase),
  654. // make a list of angles that the cylinder should traverse:
  655. angles = [];
  656. // first of all equally spaced around the cylinder:
  657. for (var i = 0; i < resolution; i++) {
  658. angles.push(i * Math.PI * 2 / resolution);
  659. }
  660. // and also at every normal of all touching planes:
  661. for (var i = 0, iMax = vertexpair.planenormals.length; i < iMax; i++) {
  662. var planenormal = vertexpair.planenormals[i],
  663. si = ybase.dot(planenormal),
  664. co = xbase.dot(planenormal),
  665. angle = Math.atan2(si, co);
  666. if (angle < 0) angle += Math.PI * 2;
  667. angles.push(angle);
  668. angle = Math.atan2(-si, -co);
  669. if (angle < 0) angle += Math.PI * 2;
  670. angles.push(angle);
  671. }
  672. // this will result in some duplicate angles but we will get rid of those later.
  673. // Sort:
  674. angles = angles.sort(fnNumberSort);
  675. // Now construct the cylinder by traversing all angles:
  676. var numangles = angles.length,
  677. prevp1, prevp2,
  678. startfacevertices = [],
  679. endfacevertices = [],
  680. polygons = [];
  681. for (var i = -1; i < numangles; i++) {
  682. var angle = angles[(i < 0) ? (i + numangles) : i],
  683. si = Math.sin(angle),
  684. co = Math.cos(angle),
  685. p = xbase.times(co * radius).plus(ybase.times(si * radius)),
  686. p1 = startpoint.plus(p),
  687. p2 = endpoint.plus(p),
  688. skip = false;
  689. if (i >= 0) {
  690. if (p1.distanceTo(prevp1) < 1e-5) {
  691. skip = true;
  692. }
  693. }
  694. if (!skip) {
  695. if (i >= 0) {
  696. startfacevertices.push(new CSG.Vertex(p1));
  697. endfacevertices.push(new CSG.Vertex(p2));
  698. var polygonvertices = [
  699. new CSG.Vertex(prevp2),
  700. new CSG.Vertex(p2),
  701. new CSG.Vertex(p1),
  702. new CSG.Vertex(prevp1)
  703. ];
  704. var polygon = new CSG.Polygon(polygonvertices);
  705. polygons.push(polygon);
  706. }
  707. prevp1 = p1;
  708. prevp2 = p2;
  709. }
  710. }
  711. endfacevertices.reverse();
  712. polygons.push(new CSG.Polygon(startfacevertices));
  713. polygons.push(new CSG.Polygon(endfacevertices));
  714. var cylinder = CSG.fromPolygons(polygons);
  715. result = result.unionSub(cylinder, false, false);
  716. }
  717. // make a list of all unique vertices
  718. // For each vertex we also collect the list of normals of the planes touching the vertices
  719. var vertexmap = {};
  720. csg.polygons.map(function(polygon) {
  721. polygon.vertices.map(function(vertex) {
  722. var vertextag = vertex.getTag();
  723. var obj;
  724. if (vertextag in vertexmap) {
  725. obj = vertexmap[vertextag];
  726. } else {
  727. obj = {
  728. pos: vertex.pos,
  729. normals: []
  730. };
  731. vertexmap[vertextag] = obj;
  732. }
  733. obj.normals.push(polygon.plane.normal);
  734. });
  735. });
  736. // and build spheres at each vertex
  737. // We will try to set the x and z axis to the normals of 2 planes
  738. // This will ensure that our sphere tesselation somewhat matches 2 planes
  739. for (var vertextag in vertexmap) {
  740. var vertexobj = vertexmap[vertextag];
  741. // use the first normal to be the x axis of our sphere:
  742. var xaxis = vertexobj.normals[0].unit();
  743. // and find a suitable z axis. We will use the normal which is most perpendicular to the x axis:
  744. var bestzaxis = null;
  745. var bestzaxisorthogonality = 0;
  746. for (var i = 1; i < vertexobj.normals.length; i++) {
  747. var normal = vertexobj.normals[i].unit();
  748. var cross = xaxis.cross(normal);
  749. var crosslength = cross.length();
  750. if (crosslength > 0.05) {
  751. if (crosslength > bestzaxisorthogonality) {
  752. bestzaxisorthogonality = crosslength;
  753. bestzaxis = normal;
  754. }
  755. }
  756. }
  757. if (!bestzaxis) {
  758. bestzaxis = xaxis.randomNonParallelVector();
  759. }
  760. var yaxis = xaxis.cross(bestzaxis).unit();
  761. var zaxis = yaxis.cross(xaxis);
  762. var sphere = CSG.sphere({
  763. center: vertexobj.pos,
  764. radius: radius,
  765. resolution: resolution,
  766. axes: [xaxis, yaxis, zaxis]
  767. });
  768. result = result.unionSub(sphere, false, false);
  769. }
  770. return result;
  771. },
  772. canonicalized: function() {
  773. if (this.isCanonicalized) {
  774. return this;
  775. } else {
  776. var factory = new CSG.fuzzyCSGFactory();
  777. var result = factory.getCSG(this);
  778. result.isCanonicalized = true;
  779. result.isRetesselated = this.isRetesselated;
  780. result.properties = this.properties; // keep original properties
  781. return result;
  782. }
  783. },
  784. reTesselated: function() {
  785. if (this.isRetesselated) {
  786. return this;
  787. } else {
  788. var csg = this;
  789. var polygonsPerPlane = {};
  790. var isCanonicalized = csg.isCanonicalized;
  791. var fuzzyfactory = new CSG.fuzzyCSGFactory();
  792. csg.polygons.map(function(polygon) {
  793. var plane = polygon.plane;
  794. var shared = polygon.shared;
  795. if (!isCanonicalized) {
  796. // in order to identify to polygons having the same plane, we need to canonicalize the planes
  797. // We don't have to do a full canonizalization (including vertices), to save time only do the planes and the shared data:
  798. plane = fuzzyfactory.getPlane(plane);
  799. shared = fuzzyfactory.getPolygonShared(shared);
  800. }
  801. var tag = plane.getTag() + "/" + shared.getTag();
  802. if (!(tag in polygonsPerPlane)) {
  803. polygonsPerPlane[tag] = [polygon];
  804. } else {
  805. polygonsPerPlane[tag].push(polygon);
  806. }
  807. });
  808. var destpolygons = [];
  809. for (var planetag in polygonsPerPlane) {
  810. var sourcepolygons = polygonsPerPlane[planetag];
  811. if (sourcepolygons.length < 2) {
  812. destpolygons = destpolygons.concat(sourcepolygons);
  813. } else {
  814. var retesselayedpolygons = [];
  815. CSG.reTesselateCoplanarPolygons(sourcepolygons, retesselayedpolygons);
  816. destpolygons = destpolygons.concat(retesselayedpolygons);
  817. }
  818. }
  819. var result = CSG.fromPolygons(destpolygons);
  820. result.isRetesselated = true;
  821. // result = result.canonicalized();
  822. result.properties = this.properties; // keep original properties
  823. return result;
  824. }
  825. },
  826. // returns an array of two CSG.Vector3Ds (minimum coordinates and maximum coordinates)
  827. getBounds: function() {
  828. if (!this.cachedBoundingBox) {
  829. var minpoint = new CSG.Vector3D(0, 0, 0);
  830. var maxpoint = new CSG.Vector3D(0, 0, 0);
  831. var polygons = this.polygons;
  832. var numpolygons = polygons.length;
  833. for (var i = 0; i < numpolygons; i++) {
  834. var polygon = polygons[i];
  835. var bounds = polygon.boundingBox();
  836. if (i === 0) {
  837. minpoint = bounds[0];
  838. maxpoint = bounds[1];
  839. } else {
  840. minpoint = minpoint.min(bounds[0]);
  841. maxpoint = maxpoint.max(bounds[1]);
  842. }
  843. }
  844. this.cachedBoundingBox = [minpoint, maxpoint];
  845. }
  846. return this.cachedBoundingBox;
  847. },
  848. // returns an object with a center (3D array) and radius (number)
  849. getBoundingSphere: function() {
  850. if (!this.cachedBoundingSphere) {
  851. var aabb;
  852. if (!this.cachedBoundingBox)
  853. aabb = this.getBounds();
  854. else aabb = this.cachedBoundingBox;
  855. var sphere = {center: aabb[0].plus(aabb[1]).dividedBy(2), radius: 0 };
  856. for (var i = 0; i < this.polygons.length; i++) {
  857. for (var j = 0; j < this.polygons[i].vertices.length; j++) {
  858. var v = this.polygons[i].vertices[j];
  859. sphere.radius = Math.max(sphere.radius,
  860. new CSG.Vector3D(v.pos.x, v.pos.y, v.pos.z).minus(sphere.center).lengthSquared());
  861. }
  862. }
  863. sphere.radius = Math.sqrt(sphere.radius);
  864. this.cachedBoundingSphere = sphere;
  865. }
  866. return this.cachedBoundingSphere;
  867. },
  868. // getBoundingSphere: function(aabb) {
  869. // // console.log(aabb);
  870. // // the sphere center is halfway between the min and max points for each coordinate
  871. // // to get the radius, go through all vertices (yuck) and test their distance from the center
  872. // // pick the biggest, and that's the radius.
  873. // // I use lengthSquared for per-vertex calcualations to avoid doing a square root on each vertex.
  874. // var sphere = {center: aabb[0].plus(aabb[1]).dividedBy(2), radius: 0 };
  875. // for (var i = 0; i < this.currentObject.polygons.length; i++) {
  876. // for (var j = 0; j < this.currentObject.polygons[i].vertices.length; j++) {
  877. // var v = this.currentObject.polygons[i].vertices[j];
  878. // sphere.radius = Math.max(sphere.radius,
  879. // new CSG.Vector3D(v.pos.x, v.pos.y, v.pos.z).minus(sphere.center).lengthSquared());
  880. // }
  881. // }
  882. // sphere.radius = Math.sqrt(sphere.radius);
  883. // return sphere;
  884. // },
  885. // returns true if there is a possibility that the two solids overlap
  886. // returns false if we can be sure that they do not overlap
  887. mayOverlap: function(csg) {
  888. if ((this.polygons.length === 0) || (csg.polygons.length === 0)) {
  889. return false;
  890. } else {
  891. var mybounds = this.getBounds();
  892. var otherbounds = csg.getBounds();
  893. if (mybounds[1].x < otherbounds[0].x) return false;
  894. if (mybounds[0].x > otherbounds[1].x) return false;
  895. if (mybounds[1].y < otherbounds[0].y) return false;
  896. if (mybounds[0].y > otherbounds[1].y) return false;
  897. if (mybounds[1].z < otherbounds[0].z) return false;
  898. if (mybounds[0].z > otherbounds[1].z) return false;
  899. return true;
  900. }
  901. },
  902. // Cut the solid by a plane. Returns the solid on the back side of the plane
  903. cutByPlane: function(plane) {
  904. if (this.polygons.length === 0) {
  905. return new CSG();
  906. }
  907. // Ideally we would like to do an intersection with a polygon of inifinite size
  908. // but this is not supported by our implementation. As a workaround, we will create
  909. // a cube, with one face on the plane, and a size larger enough so that the entire
  910. // solid fits in the cube.
  911. // find the max distance of any vertex to the center of the plane:
  912. var planecenter = plane.normal.times(plane.w);
  913. var maxdistance = 0;
  914. this.polygons.map(function(polygon) {
  915. polygon.vertices.map(function(vertex) {
  916. var distance = vertex.pos.distanceToSquared(planecenter);
  917. if (distance > maxdistance) maxdistance = distance;
  918. });
  919. });
  920. maxdistance = Math.sqrt(maxdistance);
  921. maxdistance *= 1.01; // make sure it's really larger
  922. // Now build a polygon on the plane, at any point farther than maxdistance from the plane center:
  923. var vertices = [];
  924. var orthobasis = new CSG.OrthoNormalBasis(plane);
  925. vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(maxdistance, -maxdistance))));
  926. vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(-maxdistance, -maxdistance))));
  927. vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(-maxdistance, maxdistance))));
  928. vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(maxdistance, maxdistance))));
  929. var polygon = new CSG.Polygon(vertices, null, plane.flipped());
  930. // and extrude the polygon into a cube, backwards of the plane:
  931. var cube = polygon.extrude(plane.normal.times(-maxdistance));
  932. // Now we can do the intersection:
  933. var result = this.intersect(cube);
  934. result.properties = this.properties; // keep original properties
  935. return result;
  936. },
  937. // Connect a solid to another solid, such that two CSG.Connectors become connected
  938. // myConnector: a CSG.Connector of this solid
  939. // otherConnector: a CSG.Connector to which myConnector should be connected
  940. // mirror: false: the 'axis' vectors of the connectors should point in the same direction
  941. // true: the 'axis' vectors of the connectors should point in opposite direction
  942. // normalrotation: degrees of rotation between the 'normal' vectors of the two
  943. // connectors
  944. connectTo: function(myConnector, otherConnector, mirror, normalrotation) {
  945. var matrix = myConnector.getTransformationTo(otherConnector, mirror, normalrotation);
  946. return this.transform(matrix);
  947. },
  948. // set the .shared property of all polygons
  949. // Returns a new CSG solid, the original is unmodified!
  950. setShared: function(shared) {
  951. var polygons = this.polygons.map(function(p) {
  952. return new CSG.Polygon(p.vertices, shared, p.plane);
  953. });
  954. var result = CSG.fromPolygons(polygons);
  955. result.properties = this.properties; // keep original properties
  956. result.isRetesselated = this.isRetesselated;
  957. result.isCanonicalized = this.isCanonicalized;
  958. return result;
  959. },
  960. sC: function(args) {
  961. var newshared = CSG.Polygon.Shared.fromColor.apply(this, arguments);
  962. return this.setShared(newshared);
  963. },
  964. toCompactBinary: function() {
  965. var csg = this.canonicalized(),
  966. numpolygons = csg.polygons.length,
  967. numpolygonvertices = 0,
  968. numvertices = 0,
  969. vertexmap = {},
  970. vertices = [],
  971. numplanes = 0,
  972. planemap = {},
  973. polygonindex = 0,
  974. planes = [],
  975. shareds = [],
  976. sharedmap = {},
  977. numshared = 0;
  978. // for (var i = 0, iMax = csg.polygons.length; i < iMax; i++) {
  979. // var p = csg.polygons[i];
  980. // for (var j = 0, jMax = p.length; j < jMax; j++) {
  981. // ++numpolygonvertices;
  982. // var vertextag = p[j].getTag();
  983. // if(!(vertextag in vertexmap)) {
  984. // vertexmap[vertextag] = numvertices++;
  985. // vertices.push(p[j]);
  986. // }
  987. // }
  988. csg.polygons.map(function(p) {
  989. p.vertices.map(function(v) {
  990. ++numpolygonvertices;
  991. var vertextag = v.getTag();
  992. if (!(vertextag in vertexmap)) {
  993. vertexmap[vertextag] = numvertices++;
  994. vertices.push(v);
  995. }
  996. });
  997. var planetag = p.plane.getTag();
  998. if (!(planetag in planemap)) {
  999. planemap[planetag] = numplanes++;
  1000. planes.push(p.plane);
  1001. }
  1002. var sharedtag = p.shared.getTag();
  1003. if (!(sharedtag in sharedmap)) {
  1004. sharedmap[sharedtag] = numshared++;
  1005. shareds.push(p.shared);
  1006. }
  1007. });
  1008. var numVerticesPerPolygon = new Uint32Array(numpolygons),
  1009. polygonSharedIndexes = new Uint32Array(numpolygons),
  1010. polygonVertices = new Uint32Array(numpolygonvertices),
  1011. polygonPlaneIndexes = new Uint32Array(numpolygons),
  1012. vertexData = new Float64Array(numvertices * 3),
  1013. planeData = new Float64Array(numplanes * 4),
  1014. polygonVerticesIndex = 0;
  1015. for (var polygonindex = 0; polygonindex < numpolygons; ++polygonindex) {
  1016. var p = csg.polygons[polygonindex];
  1017. numVerticesPerPolygon[polygonindex] = p.vertices.length;
  1018. p.vertices.map(function(v) {
  1019. var vertextag = v.getTag();
  1020. var vertexindex = vertexmap[vertextag];
  1021. polygonVertices[polygonVerticesIndex++] = vertexindex;
  1022. });
  1023. var planetag = p.plane.getTag();
  1024. var planeindex = planemap[planetag];
  1025. polygonPlaneIndexes[polygonindex] = planeindex;
  1026. var sharedtag = p.shared.getTag();
  1027. var sharedindex = sharedmap[sharedtag];
  1028. polygonSharedIndexes[polygonindex] = sharedindex;
  1029. }
  1030. var verticesArrayIndex = 0;
  1031. vertices.map(function(v) {
  1032. var pos = v.pos;
  1033. vertexData[verticesArrayIndex++] = pos._x;
  1034. vertexData[verticesArrayIndex++] = pos._y;
  1035. vertexData[verticesArrayIndex++] = pos._z;
  1036. });
  1037. var planesArrayIndex = 0;
  1038. planes.map(function(p) {
  1039. var normal = p.normal;
  1040. planeData[planesArrayIndex++] = normal._x;
  1041. planeData[planesArrayIndex++] = normal._y;
  1042. planeData[planesArrayIndex++] = normal._z;
  1043. planeData[planesArrayIndex++] = p.w;
  1044. });
  1045. var result = {
  1046. "class": "CSG",
  1047. numPolygons: numpolygons,
  1048. numVerticesPerPolygon: numVerticesPerPolygon,
  1049. polygonPlaneIndexes: polygonPlaneIndexes,
  1050. polygonSharedIndexes: polygonSharedIndexes,
  1051. polygonVertices: polygonVertices,
  1052. vertexData: vertexData,
  1053. planeData: planeData,
  1054. shared: shareds
  1055. };
  1056. return result;
  1057. },
  1058. // For debugging
  1059. // Creates a new solid with a tiny cube at every vertex of the source solid
  1060. toPointCloud: function(cuberadius) {
  1061. var csg = this.reTesselated();
  1062. var result = new CSG();
  1063. // make a list of all unique vertices
  1064. // For each vertex we also collect the list of normals of the planes touching the vertices
  1065. var vertexmap = {};
  1066. csg.polygons.map(function(polygon) {
  1067. polygon.vertices.map(function(vertex) {
  1068. vertexmap[vertex.getTag()] = vertex.pos;
  1069. });
  1070. });
  1071. for (var vertextag in vertexmap) {
  1072. var pos = vertexmap[vertextag];
  1073. var cube = CSG.cube({
  1074. center: pos,
  1075. radius: cuberadius
  1076. });
  1077. result = result.unionSub(cube, false, false);
  1078. }
  1079. result = result.reTesselated();
  1080. return result;
  1081. },
  1082. // Get the transformation that transforms this CSG such that it is lying on the z=0 plane,
  1083. // as flat as possible (i.e. the least z-height).
  1084. // So that it is in an orientation suitable for CNC milling
  1085. getTransformationAndInverseTransformationToFlatLying: function() {
  1086. if (this.polygons.length === 0) {
  1087. var m = new CSG.Matrix4x4(); // unity
  1088. return [m,m];
  1089. } else {
  1090. // get a list of unique planes in the CSG:
  1091. var csg = this.canonicalized();
  1092. var planemap = {};
  1093. csg.polygons.map(function(polygon) {
  1094. planemap[polygon.plane.getTag()] = polygon.plane;
  1095. });
  1096. // try each plane in the CSG and find the plane that, when we align it flat onto z=0,
  1097. // gives the least height in z-direction.
  1098. // If two planes give the same height, pick the plane that originally had a normal closest
  1099. // to [0,0,-1].
  1100. var xvector = new CSG.Vector3D(1, 0, 0);
  1101. var yvector = new CSG.Vector3D(0, 1, 0);
  1102. var zvector = new CSG.Vector3D(0, 0, 1);
  1103. var z0connectorx = new CSG.Connector([0, 0, 0], [0, 0, -1], xvector);
  1104. var z0connectory = new CSG.Connector([0, 0, 0], [0, 0, -1], yvector);
  1105. var isfirst = true;
  1106. var minheight = 0;
  1107. var maxdotz = 0;
  1108. var besttransformation, bestinversetransformation;
  1109. for (var planetag in planemap) {
  1110. var plane = planemap[planetag];
  1111. var pointonplane = plane.normal.times(plane.w);
  1112. var transformation, inversetransformation;
  1113. // We need a normal vecrtor for the transformation
  1114. // determine which is more perpendicular to the plane normal: x or y?
  1115. // we will align this as much as possible to the x or y axis vector
  1116. var xorthogonality = plane.normal.cross(xvector).length();
  1117. var yorthogonality = plane.normal.cross(yvector).length();
  1118. if (xorthogonality > yorthogonality) {
  1119. // x is better:
  1120. var planeconnector = new CSG.Connector(pointonplane, plane.normal, xvector);
  1121. transformation = planeconnector.getTransformationTo(z0connectorx, false, 0);
  1122. inversetransformation = z0connectorx.getTransformationTo(planeconnector, false, 0);
  1123. } else {
  1124. // y is better:
  1125. var planeconnector = new CSG.Connector(pointonplane, plane.normal, yvector);
  1126. transformation = planeconnector.getTransformationTo(z0connectory, false, 0);
  1127. inversetransformation = z0connectory.getTransformationTo(planeconnector, false, 0);
  1128. }
  1129. var transformedcsg = csg.transform(transformation);
  1130. var dotz = -plane.normal.dot(zvector);
  1131. var bounds = transformedcsg.getBounds();
  1132. var zheight = bounds[1].z - bounds[0].z;
  1133. var isbetter = isfirst;
  1134. if (!isbetter) {
  1135. if (zheight < minheight) {
  1136. isbetter = true;
  1137. } else if (zheight == minheight) {
  1138. if (dotz > maxdotz) isbetter = true;
  1139. }
  1140. }
  1141. if (isbetter) {
  1142. // translate the transformation around the z-axis and onto the z plane:
  1143. var translation = new CSG.Vector3D([-0.5 * (bounds[1].x + bounds[0].x), -0.5 * (bounds[1].y + bounds[0].y), -bounds[0].z]);
  1144. transformation = transformation.multiply(CSG.Matrix4x4.translation(translation));
  1145. inversetransformation = CSG.Matrix4x4.translation(translation.negated()).multiply(inversetransformation);
  1146. minheight = zheight;
  1147. maxdotz = dotz;
  1148. besttransformation = transformation;
  1149. bestinversetransformation = inversetransformation;
  1150. }
  1151. isfirst = false;
  1152. }
  1153. return [besttransformation, bestinversetransformation];
  1154. }
  1155. },
  1156. getTransformationToFlatLying: function() {
  1157. var result = this.getTransformationAndInverseTransformationToFlatLying();
  1158. return result[0];
  1159. },
  1160. lieFlat: function() {
  1161. var transformation = this.getTransformationToFlatLying();
  1162. return this.transform(transformation);
  1163. },
  1164. // project the 3D CSG onto a plane
  1165. // This returns a 2D CAG with the 'shadow' shape of the 3D solid when projected onto the
  1166. // plane represented by the orthonormal basis
  1167. projectToOrthoNormalBasis: function(orthobasis) {
  1168. var EPS = 1e-5;
  1169. var cags = [];
  1170. this.polygons.filter(function(p) {
  1171. // only return polys in plane, others may disturb result
  1172. return p.plane.normal.minus(orthobasis.plane.normal).lengthSquared() < EPS*EPS;
  1173. })
  1174. .map(function(polygon) {
  1175. var cag = polygon.projectToOrthoNormalBasis(orthobasis);
  1176. if (cag.sides.length > 0) {
  1177. cags.push(cag);
  1178. }
  1179. });
  1180. var result = new CAG().union(cags);
  1181. return result;
  1182. },
  1183. sectionCut: function(orthobasis) {
  1184. var EPS = 1e-5;
  1185. var plane1 = orthobasis.plane;
  1186. var plane2 = orthobasis.plane.flipped();
  1187. plane1 = new CSG.Plane(plane1.normal, plane1.w);
  1188. plane2 = new CSG.Plane(plane2.normal, plane2.w + 5*EPS);
  1189. var cut3d = this.cutByPlane(plane1);
  1190. cut3d = cut3d.cutByPlane(plane2);
  1191. return cut3d.projectToOrthoNormalBasis(orthobasis);
  1192. },
  1193. // fixTJunctionsNew
  1194. // This TJunction fixer will add a new polygon into the TJunctions.
  1195. // I am trying to fix solids for taper and other non-linear transforms.
  1196. // Algorithm:
  1197. // get all unpaired edges. (I'll steal this from old FixTJunctions).
  1198. // go through unpaired edges. Look for a reverse edge that matches one side.
  1199. // see if you can follow a chain that brings to you the original edge again.
  1200. // that chain becomes a new polygon.
  1201. // add that polygon onto the csg.
  1202. /*
  1203. fixTJunctions:
  1204. Suppose we have two polygons ACDB and EDGF:
  1205. A-----B
  1206. | |
  1207. | E--F
  1208. | | |
  1209. C-----D--G
  1210. Note that vertex E forms a T-junction on the side BD. In this case some STL slicers will complain
  1211. that the solid is not watertight. This is because the watertightness check is done by checking if
  1212. each side DE is matched by another side ED.
  1213. This function will return a new solid with ACDB replaced by ACDEB
  1214. Note that this can create polygons that are slightly non-convex (due to rounding errors). Therefore the result should
  1215. not be used for further CSG operations!
  1216. */
  1217. // fixTJunctions: function() {
  1218. // var csg = this.canonicalized();
  1219. // var sidemap = {};
  1220. // for (var polygonindex = 0; polygonindex < csg.polygons.length; polygonindex++) {
  1221. // var polygon = csg.polygons[polygonindex];
  1222. // var numvertices = polygon.vertices.length;
  1223. // if (numvertices >= 3) // should be true
  1224. // {
  1225. // var vertex = polygon.vertices[0];
  1226. // var vertextag = vertex.getTag();
  1227. // for (var vertexindex = 0; vertexindex < numvertices; vertexindex++) {
  1228. // var nextvertexindex = vertexindex + 1;
  1229. // if (nextvertexindex == numvertices) nextvertexindex = 0;
  1230. // var nextvertex = polygon.vertices[nextvertexindex];
  1231. // var nextvertextag = nextvertex.getTag();
  1232. // var sidetag = vertextag + "/" + nextvertextag;
  1233. // var reversesidetag = nextvertextag + "/" + vertextag;
  1234. // if (reversesidetag in sidemap) {
  1235. // // this side matches the same side in another polygon. Remove from sidemap:
  1236. // var ar = sidemap[reversesidetag];
  1237. // ar.splice(-1, 1);
  1238. // if (ar.length === 0) {
  1239. // delete sidemap[reversesidetag];
  1240. // }
  1241. // } else {
  1242. // var sideobj = {
  1243. // vertex0: vertex,
  1244. // vertex1: nextvertex,
  1245. // polygonindex: polygonindex
  1246. // };
  1247. // if (!(sidetag in sidemap)) {
  1248. // sidemap[sidetag] = [sideobj];
  1249. // } else {
  1250. // sidemap[sidetag].push(sideobj);
  1251. // }
  1252. // }
  1253. // vertex = nextvertex;
  1254. // vertextag = nextvertextag;
  1255. // }
  1256. // }
  1257. // }
  1258. // // now sidemap contains 'unmatched' sides
  1259. // // i.e. side AB in one polygon does not have a matching side BA in another polygon
  1260. // var vertextag2sidestart = {};
  1261. // var vertextag2sideend = {};
  1262. // var sidestocheck = {};
  1263. // var sidemapisempty = true;
  1264. // for (var sidetag in sidemap) {
  1265. // sidemapisempty = false;
  1266. // sidestocheck[sidetag] = true;
  1267. // sidemap[sidetag].map(function(sideobj) {
  1268. // var starttag = sideobj.vertex0.getTag();
  1269. // var endtag = sideobj.vertex1.getTag();
  1270. // if (starttag in vertextag2sidestart) {
  1271. // vertextag2sidestart[starttag].push(sidetag);
  1272. // } else {
  1273. // vertextag2sidestart[starttag] = [sidetag];
  1274. // }
  1275. // if (endtag in vertextag2sideend) {
  1276. // vertextag2sideend[endtag].push(sidetag);
  1277. // } else {
  1278. // vertextag2sideend[endtag] = [sidetag];
  1279. // }
  1280. // });
  1281. // }
  1282. // if (!sidemapisempty) {
  1283. // // make a copy of the polygons array, since we are going to modify it:
  1284. // var polygons = csg.polygons.slice(0);
  1285. // function addSide(vertex0, vertex1, polygonindex) {
  1286. // var starttag = vertex0.getTag();
  1287. // var endtag = vertex1.getTag();
  1288. // if (starttag == endtag) throw new Error("Assertion failed");
  1289. // var newsidetag = starttag + "/" + endtag;
  1290. // var reversesidetag = endtag + "/" + starttag;
  1291. // if (reversesidetag in sidemap) {
  1292. // // we have a matching reverse oriented side.
  1293. // // Instead of adding the new side, cancel out the reverse side:
  1294. // // console.log("addSide("+newsidetag+") has reverse side:");
  1295. // deleteSide(vertex1, vertex0, null);
  1296. // return null;
  1297. // }
  1298. // // console.log("addSide("+newsidetag+")");
  1299. // var newsideobj = {
  1300. // vertex0: vertex0,
  1301. // vertex1: vertex1,
  1302. // polygonindex: polygonindex
  1303. // };
  1304. // if (!(newsidetag in sidemap)) {
  1305. // sidemap[newsidetag] = [newsideobj];
  1306. // } else {
  1307. // sidemap[newsidetag].push(newsideobj);
  1308. // }
  1309. // if (starttag in vertextag2sidestart) {
  1310. // vertextag2sidestart[starttag].push(newsidetag);
  1311. // } else {
  1312. // vertextag2sidestart[starttag] = [newsidetag];
  1313. // }
  1314. // if (endtag in vertextag2sideend) {
  1315. // vertextag2sideend[endtag].push(newsidetag);
  1316. // } else {
  1317. // vertextag2sideend[endtag] = [newsidetag];
  1318. // }
  1319. // return newsidetag;
  1320. // }
  1321. // function deleteSide(vertex0, vertex1, polygonindex) {
  1322. // var starttag = vertex0.getTag();
  1323. // var endtag = vertex1.getTag();
  1324. // var sidetag = starttag + "/" + endtag;
  1325. // // console.log("deleteSide("+sidetag+")");
  1326. // if (!(sidetag in sidemap)) throw new Error("Assertion failed");
  1327. // var idx = -1;
  1328. // var sideobjs = sidemap[sidetag];
  1329. // for (var i = 0; i < sideobjs.length; i++) {
  1330. // var sideobj = sideobjs[i];
  1331. // if (sideobj.vertex0 != vertex0) continue;
  1332. // if (sideobj.vertex1 != vertex1) continue;
  1333. // if (polygonindex !== null) {
  1334. // if (sideobj.polygonindex != polygonindex) continue;
  1335. // }
  1336. // idx = i;
  1337. // break;
  1338. // }
  1339. // if (idx < 0) throw new Error("Assertion failed");
  1340. // sideobjs.splice(idx, 1);
  1341. // if (sideobjs.length === 0) {
  1342. // delete sidemap[sidetag];
  1343. // }
  1344. // idx = vertextag2sidestart[starttag].indexOf(sidetag);
  1345. // if (idx < 0) throw new Error("Assertion failed");
  1346. // vertextag2sidestart[starttag].splice(idx, 1);
  1347. // if (vertextag2sidestart[starttag].length === 0) {
  1348. // delete vertextag2sidestart[starttag];
  1349. // }
  1350. // idx = vertextag2sideend[endtag].indexOf(sidetag);
  1351. // if (idx < 0) throw new Error("Assertion failed");
  1352. // vertextag2sideend[endtag].splice(idx, 1);
  1353. // if (vertextag2sideend[endtag].length === 0) {
  1354. // delete vertextag2sideend[endtag];
  1355. // }
  1356. // }
  1357. // while (true) {
  1358. // var sidemapisempty = true;
  1359. // for (var sidetag in sidemap) {
  1360. // sidemapisempty = false;
  1361. // sidestocheck[sidetag] = true;
  1362. // }
  1363. // if (sidemapisempty) break;
  1364. // var donesomething = false;
  1365. // while (true) {
  1366. // var sidetagtocheck = null;
  1367. // for (var sidetag in sidestocheck) {
  1368. // sidetagtocheck = sidetag;
  1369. // break;
  1370. // }
  1371. // if (sidetagtocheck === null) break; // sidestocheck is empty, we're done!
  1372. // var donewithside = true;
  1373. // if (sidetagtocheck in sidemap) {
  1374. // var sideobjs = sidemap[sidetagtocheck];
  1375. // if (sideobjs.length === 0) throw new Error("Assertion failed");
  1376. // var sideobj = sideobjs[0];
  1377. // for (var directionindex = 0; directionindex < 2; directionindex++) {
  1378. // var startvertex = (directionindex === 0) ? sideobj.vertex0 : sideobj.vertex1;
  1379. // var endvertex = (directionindex === 0) ? sideobj.vertex1 : sideobj.vertex0;
  1380. // var startvertextag = startvertex.getTag();
  1381. // var endvertextag = endvertex.getTag();
  1382. // var matchingsides = [];
  1383. // if (directionindex === 0) {
  1384. // if (startvertextag in vertextag2sideend) {
  1385. // matchingsides = vertextag2sideend[startvertextag];
  1386. // }
  1387. // } else {
  1388. // if (startvertextag in vertextag2sidestart) {
  1389. // matchingsides = vertextag2sidestart[startvertextag];
  1390. // }
  1391. // }
  1392. // for (var matchingsideindex = 0; matchingsideindex < matchingsides.length; matchingsideindex++) {
  1393. // var matchingsidetag = matchingsides[matchingsideindex];
  1394. // var matchingside = sidemap[matchingsidetag][0];
  1395. // var matchingsidestartvertex = (directionindex === 0) ? matchingside.vertex0 : matchingside.vertex1;
  1396. // var matchingsideendvertex = (directionindex === 0) ? matchingside.vertex1 : matchingside.vertex0;
  1397. // var matchingsidestartvertextag = matchingsidestartvertex.getTag();
  1398. // var matchingsideendvertextag = matchingsideendvertex.getTag();
  1399. // if (matchingsideendvertextag != startvertextag) throw new Error("Assertion failed");
  1400. // if (matchingsidestartvertextag == endvertextag) {
  1401. // // matchingside cancels sidetagtocheck
  1402. // deleteSide(startvertex, endvertex, null);
  1403. // deleteSide(endvertex, startvertex, null);
  1404. // donewithside = false;
  1405. // directionindex = 2; // skip reverse direction check
  1406. // donesomething = true;
  1407. // break;
  1408. // } else {
  1409. // var startpos = startvertex.pos;
  1410. // var endpos = endvertex.pos;
  1411. // var checkpos = matchingsidestartvertex.pos;
  1412. // var direction = checkpos.minus(startpos);
  1413. // // Now we need to check if endpos is on the line startpos-checkpos:
  1414. // var t = endpos.minus(startpos).dot(direction) / direction.dot(direction);
  1415. // if ((t > 0) && (t < 1)) {
  1416. // var closestpoint = startpos.plus(direction.times(t));
  1417. // var distancesquared = closestpoint.distanceToSquared(endpos);
  1418. // if (distancesquared < 1e-10) {
  1419. // // Yes it's a t-junction! We need to split matchingside in two:
  1420. // var polygonindex = matchingside.polygonindex;
  1421. // var polygon = polygons[polygonindex];
  1422. // // find the index of startvertextag in polygon:
  1423. // var insertionvertextag = matchingside.vertex1.getTag();
  1424. // var insertionvertextagindex = -1;
  1425. // for (var i = 0; i < polygon.vertices.length; i++) {
  1426. // if (polygon.vertices[i].getTag() == insertionvertextag) {
  1427. // insertionvertextagindex = i;
  1428. // break;
  1429. // }
  1430. // }
  1431. // if (insertionvertextagindex < 0) throw new Error("Assertion failed");
  1432. // // split the side by inserting the vertex:
  1433. // var newvertices = polygon.vertices.slice(0);
  1434. // newvertices.splice(insertionvertextagindex, 0, endvertex);
  1435. // var newpolygon = new CSG.Polygon(newvertices, polygon.shared /*polygon.plane*/ );
  1436. // polygons[polygonindex] = newpolygon;
  1437. // // remove the original sides from our maps:
  1438. // // deleteSide(sideobj.vertex0, sideobj.vertex1, null);
  1439. // deleteSide(matchingside.vertex0, matchingside.vertex1, polygonindex);
  1440. // var newsidetag1 = addSide(matchingside.vertex0, endvertex, polygonindex);
  1441. // var newsidetag2 = addSide(endvertex, matchingside.vertex1, polygonindex);
  1442. // if (newsidetag1 !== null) sidestocheck[newsidetag1] = true;
  1443. // if (newsidetag2 !== null) sidestocheck[newsidetag2] = true;
  1444. // donewithside = false;
  1445. // directionindex = 2; // skip reverse direction check
  1446. // donesomething = true;
  1447. // break;
  1448. // } // if(distancesquared < 1e-10)
  1449. // } // if( (t > 0) && (t < 1) )
  1450. // } // if(endingstidestartvertextag == endvertextag)
  1451. // } // for matchingsideindex
  1452. // } // for directionindex
  1453. // } // if(sidetagtocheck in sidemap)
  1454. // if (donewithside) {
  1455. // delete sidestocheck[sidetag];
  1456. // }
  1457. // }
  1458. // if (!donesomething) break;
  1459. // }
  1460. // var newcsg = CSG.fromPolygons(polygons);
  1461. // newcsg.properties = csg.properties;
  1462. // newcsg.isCanonicalized = true;
  1463. // newcsg.isRetesselated = true;
  1464. // csg = newcsg;
  1465. // } // if(!sidemapisempty)
  1466. // var sidemapisempty = true;
  1467. // for (var sidetag in sidemap) {
  1468. // sidemapisempty = false;
  1469. // break;
  1470. // }
  1471. // if (!sidemapisempty) {
  1472. // // throw new Error("!sidemapisempty");
  1473. // console.log("!sidemapisempty");
  1474. // }
  1475. // return csg;
  1476. // },
  1477. toTriangles: function() {
  1478. var polygons = [];
  1479. this.polygons.forEach(function(poly) {
  1480. var firstVertex = poly.vertices[0];
  1481. for (var i = poly.vertices.length - 3; i >= 0; i--) {
  1482. polygons.push(new CSG.Polygon([
  1483. firstVertex, poly.vertices[i + 1], poly.vertices[i + 2]
  1484. ],
  1485. poly.shared, poly.plane));
  1486. }
  1487. });
  1488. return polygons;
  1489. },
  1490. // features: string, or array containing 1 or more strings of: 'volume', 'area'
  1491. // more could be added here (Fourier coeff, moments)
  1492. getFeatures: function(features) {
  1493. if (!(features instanceof Array)) {
  1494. features = [features];
  1495. }
  1496. var result = this.toTriangles().map(function(triPoly) {
  1497. return triPoly.getTetraFeatures(features);
  1498. })
  1499. .reduce(function(pv, v) {
  1500. return v.map(function(feat, i) {
  1501. return feat + (pv === 0 ? 0 : pv[i]);
  1502. });
  1503. }, 0);
  1504. return (result.length == 1) ? result[0] : result;
  1505. }
  1506. };
  1507. // Parse an option from the options object
  1508. // If the option is not present, return the default value
  1509. CSG.parseOption = function(options, optionname, defaultvalue) {
  1510. var result = defaultvalue;
  1511. if (options) {
  1512. if (optionname in options) {
  1513. result = options[optionname];
  1514. }
  1515. }
  1516. return result;
  1517. };
  1518. // Parse an option and force into a CSG.Vector3D. If a scalar is passed it is converted
  1519. // into a vector with equal x,y,z
  1520. CSG.parseOptionAs3DVector = function(options, optionname, defaultvalue) {
  1521. var result = CSG.parseOption(options, optionname, defaultvalue);
  1522. result = new CSG.Vector3D(result);
  1523. return result;
  1524. };
  1525. CSG.parseOptionAs3DVectorList = function(options, optionname, defaultvalue) {
  1526. var result = CSG.parseOption(options, optionname, defaultvalue);
  1527. return result.map(function(res) {
  1528. return new CSG.Vector3D(res);
  1529. });
  1530. };
  1531. // Parse an option and force into a CSG.Vector2D. If a scalar is passed it is converted
  1532. // into a vector with equal x,y
  1533. CSG.parseOptionAs2DVector = function(options, optionname, defaultvalue) {
  1534. var result = CSG.parseOption(options, optionname, defaultvalue);
  1535. result = new CSG.Vector2D(result);
  1536. return result;
  1537. };
  1538. CSG.parseOptionAsFloat = function(options, optionname, defaultvalue) {
  1539. var result = CSG.parseOption(options, optionname, defaultvalue);
  1540. if (typeof(result) == "string") {
  1541. result = Number(result);
  1542. }
  1543. if (isNaN(result) || typeof(result) != "number") {
  1544. throw new Error("Parameter " + optionname + " should be a number");
  1545. }
  1546. return result;
  1547. };
  1548. CSG.parseOptionAsInt = function(options, optionname, defaultvalue) {
  1549. var result = CSG.parseOption(options, optionname, defaultvalue);
  1550. result = Number(Math.floor(result));
  1551. if (isNaN(result)) {
  1552. throw new Error("Parameter " + optionname + " should be a number");
  1553. }
  1554. return result;
  1555. };
  1556. CSG.parseOptionAsBool = function(options, optionname, defaultvalue) {
  1557. var result = CSG.parseOption(options, optionname, defaultvalue);
  1558. if (typeof(result) == "string") {
  1559. if (result == "true") result = true;
  1560. else if (result == "false") result = false;
  1561. else if (result == 0) result = false;
  1562. }
  1563. result = !!result;
  1564. return result;
  1565. };
  1566. // Construct an axis-aligned solid cuboid.
  1567. // Parameters:
  1568. // center: center of cube (default [0,0,0])
  1569. // radius: radius of cube (default [1,1,1]), can be specified as scalar or as 3D vector
  1570. //
  1571. // Example code:
  1572. //
  1573. // var cube = CSG.cube({
  1574. // center: [0, 0, 0],
  1575. // radius: 1
  1576. // });
  1577. CSG.cube = function(options) {
  1578. var c, r;
  1579. options = options || {};
  1580. if (('corner1' in options) || ('corner2' in options)) {
  1581. if (('center' in options) || ('radius' in options)) {
  1582. throw new Error("cube: should either give a radius and center parameter, or a corner1 and corner2 parameter")
  1583. }
  1584. corner1 = CSG.parseOptionAs3DVector(options, "corner1", [0, 0, 0]);
  1585. corner2 = CSG.parseOptionAs3DVector(options, "corner2", [1, 1, 1]);
  1586. c = corner1.plus(corner2).times(0.5);
  1587. r = corner2.minus(corner1).times(0.5);
  1588. } else {
  1589. c = CSG.parseOptionAs3DVector(options, "center", [0, 0, 0]);
  1590. r = CSG.parseOptionAs3DVector(options, "radius", [1, 1, 1]);
  1591. }
  1592. //r = r.abs(); // negative radii make no sense. Variables aren't caught by our error system.
  1593. if ((r.x < 0) || (r.y < 0) || (r.z < 0)) {
  1594. throw new Error("Dimension should be positive");
  1595. }
  1596. // throw out cubes with any dimension too close to zero - JY
  1597. if (r.x < 0.0005 || r.y < 0.0005 || r.z < 0.0005){
  1598. console.log("Throwing out a zero-length cube.");
  1599. return new CSG;
  1600. }
  1601. var result = CSG.fromPolygons([
  1602. [
  1603. [0, 4, 6, 2],
  1604. [-1, 0, 0]
  1605. ],
  1606. [
  1607. [1, 3, 7, 5],
  1608. [+1, 0, 0]
  1609. ],
  1610. [
  1611. [0, 1, 5, 4],
  1612. [0, -1, 0]
  1613. ],
  1614. [
  1615. [2, 6, 7, 3],
  1616. [0, +1, 0]
  1617. ],
  1618. [
  1619. [0, 2, 3, 1],
  1620. [0, 0, -1]
  1621. ],
  1622. [
  1623. [4, 5, 7, 6],
  1624. [0, 0, +1]
  1625. ]
  1626. ].map(function(info) {
  1627. //var normal = new CSG.Vector3D(info[1]);
  1628. //var plane = new CSG.Plane(normal, 1);
  1629. var vertices = info[0].map(function(i) {
  1630. var pos = new CSG.Vector3D(
  1631. c.x + r.x * (2 * !!(i & 1) - 1), c.y + r.y * (2 * !!(i & 2) - 1), c.z + r.z * (2 * !!(i & 4) - 1));
  1632. return new CSG.Vertex(pos);
  1633. });
  1634. return new CSG.Polygon(vertices, null /* , plane */ );
  1635. }));
  1636. result.properties.cube = new CSG.Properties();
  1637. result.properties.cube.center = new CSG.Vector3D(c);
  1638. // add 6 connectors, at the centers of each face:
  1639. result.properties.cube.facecenters = [
  1640. new CSG.Connector(new CSG.Vector3D([r.x, 0, 0]).plus(c), [1, 0, 0], [0, 0, 1]),
  1641. new CSG.Connector(new CSG.Vector3D([-r.x, 0, 0]).plus(c), [-1, 0, 0], [0, 0, 1]),
  1642. new CSG.Connector(new CSG.Vector3D([0, r.y, 0]).plus(c), [0, 1, 0], [0, 0, 1]),
  1643. new CSG.Connector(new CSG.Vector3D([0, -r.y, 0]).plus(c), [0, -1, 0], [0, 0, 1]),
  1644. new CSG.Connector(new CSG.Vector3D([0, 0, r.z]).plus(c), [0, 0, 1], [1, 0, 0]),
  1645. new CSG.Connector(new CSG.Vector3D([0, 0, -r.z]).plus(c), [0, 0, -1], [1, 0, 0])
  1646. ];
  1647. return result;
  1648. };
  1649. // Construct a solid sphere
  1650. //
  1651. // Parameters:
  1652. // center: center of sphere (default [0,0,0])
  1653. // radius: radius of sphere (default 1), must be a scalar
  1654. // resolution: determines the number of polygons per 360 degree revolution (default 12)
  1655. // axes: (optional) an array with 3 vectors for the x, y and z base vectors
  1656. //
  1657. // Example usage:
  1658. //
  1659. // var sphere = CSG.sphere({
  1660. // center: [0, 0, 0],
  1661. // radius: 2,
  1662. // resolution: 32,
  1663. // });
  1664. CSG.sphere = function(options) {
  1665. options = options || {};
  1666. var center = CSG.parseOptionAs3DVector(options, "center", [0, 0, 0]);
  1667. var radius = CSG.parseOptionAsFloat(options, "radius", 1);
  1668. var resolution = CSG.parseOptionAsInt(options, "res", CSG.defaultResolution3D);
  1669. var xvector, yvector, zvector;
  1670. if (radius < 0) {
  1671. throw new Error("Radius should be positive");
  1672. }
  1673. if(radius < 0.000001) {
  1674. console.log("Throwing out a zero-radius sphere.")
  1675. return(new CSG);
  1676. }
  1677. if ('axes' in options) {
  1678. xvector = options.axes[0].unit().times(radius);
  1679. yvector = options.axes[1].unit().times(radius);
  1680. zvector = options.axes[2].unit().times(radius);
  1681. } else {
  1682. xvector = new CSG.Vector3D([1, 0, 0]).times(radius);
  1683. yvector = new CSG.Vector3D([0, -1, 0]).times(radius);
  1684. zvector = new CSG.Vector3D([0, 0, 1]).times(radius);
  1685. }
  1686. if (resolution < 4) resolution = 4;
  1687. var qresolution = Math.round(resolution / 4);
  1688. var prevcylinderpoint;
  1689. var polygons = [];
  1690. for (var slice1 = 0; slice1 <= resolution; slice1++) {
  1691. var angle = Math.PI * 2.0 * slice1 / resolution;
  1692. var cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle)));
  1693. if (slice1 > 0) {
  1694. // cylinder vertices:
  1695. var vertices = [];
  1696. var prevcospitch, prevsinpitch;
  1697. for (var slice2 = 0; slice2 <= qresolution; slice2++) {
  1698. var pitch = 0.5 * Math.PI * slice2 / qresolution;
  1699. var cospitch = Math.cos(pitch);
  1700. var sinpitch = Math.sin(pitch);
  1701. if (slice2 > 0) {
  1702. vertices = [];
  1703. vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))));
  1704. vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))));
  1705. if (slice2 < qresolution) {
  1706. vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))));
  1707. }
  1708. vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))));
  1709. polygons.push(new CSG.Polygon(vertices));
  1710. vertices = [];
  1711. vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))));
  1712. vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))));
  1713. if (slice2 < qresolution) {
  1714. vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))));
  1715. }
  1716. vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))));
  1717. vertices.reverse();
  1718. polygons.push(new CSG.Polygon(vertices));
  1719. }
  1720. prevcospitch = cospitch;
  1721. prevsinpitch = sinpitch;
  1722. }
  1723. }
  1724. prevcylinderpoint = cylinderpoint;
  1725. }
  1726. var result = CSG.fromPolygons(polygons);
  1727. result.properties.sphere = new CSG.Properties();
  1728. result.properties.sphere.center = new CSG.Vector3D(center);
  1729. result.properties.sphere.facepoint = center.plus(xvector);
  1730. return result;
  1731. };
  1732. // Construct a solid cylinder.
  1733. //
  1734. // Parameters:
  1735. // start: start point of cylinder (default [0, -1, 0])
  1736. // end: end point of cylinder (default [0, 1, 0])
  1737. // radius: radius of cylinder (default 1), must be a scalar
  1738. // resolution: determines the number of polygons per 360 degree revolution (default 12)
  1739. //
  1740. // Example usage:
  1741. //
  1742. // var cylinder = CSG.cylinder({
  1743. // start: [0, -1, 0],
  1744. // end: [0, 1, 0],
  1745. // radius: 1,
  1746. // resolution: 16
  1747. // });
  1748. CSG.cylinder = function(options) {
  1749. var s = CSG.parseOptionAs3DVector(options, "start", [0, -1, 0]);
  1750. var e = CSG.parseOptionAs3DVector(options, "end", [0, 1, 0]);
  1751. var r = CSG.parseOptionAsFloat(options, "radius", 1);
  1752. var rEnd = CSG.parseOptionAsFloat(options, "radiusEnd", r);
  1753. var rStart = CSG.parseOptionAsFloat(options, "radiusStart", r);
  1754. var alpha = CSG.parseOptionAsFloat(options, "sectorAngle", 360);
  1755. alpha = alpha > 360 ? alpha % 360 : alpha;
  1756. if ((rEnd < 0) || (rStart < 0)) {
  1757. throw new Error("Radius should be non-negative");
  1758. }
  1759. // if ((rEnd === 0) && (rStart === 0)) {
  1760. // throw new Error("Either radiusStart or radiusEnd should be nonzero");
  1761. // }
  1762. if (Math.abs(e.z - s.z) < 0.0005 || ((rEnd < 0.0005) && (rStart < 0.0005))) {
  1763. console.log("throwing out a zero-height cylinder or a cylinder with both ends < 0.0005");
  1764. return new CSG;
  1765. }
  1766. var slices = CSG.parseOptionAsInt(options, "res", CSG.defaultResolution2D);
  1767. var ray = e.minus(s);
  1768. var axisZ = ray.unit(); //, isY = (Math.abs(axisZ.y) > 0.5);
  1769. var axisX = axisZ.randomNonParallelVector().unit();
  1770. // var axisX = new CSG.Vector3D(isY, !isY, 0).cross(axisZ).unit();
  1771. var axisY = axisX.cross(axisZ).unit();
  1772. var start = new CSG.Vertex(s);
  1773. var end = new CSG.Vertex(e);
  1774. var polygons = [];
  1775. function point(stack, slice, radius) {
  1776. var angle = slice * Math.PI * alpha / 180;
  1777. var out = axisX.times(Math.cos(angle)).plus(axisY.times(Math.sin(angle)));
  1778. var pos = s.plus(ray.times(stack)).plus(out.times(radius));
  1779. return new CSG.Vertex(pos);
  1780. }
  1781. if (alpha > 0) {
  1782. for (var i = 0; i < slices; i++) {
  1783. var t0 = i / slices,
  1784. t1 = (i + 1) / slices;
  1785. if (rEnd == rStart) {
  1786. polygons.push(new CSG.Polygon([start, point(0, t0, rEnd), point(0, t1, rEnd)]));
  1787. polygons.push(new CSG.Polygon([point(0, t1, rEnd), point(0, t0, rEnd), point(1, t0, rEnd), point(1, t1, rEnd)]));
  1788. polygons.push(new CSG.Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]));
  1789. } else {
  1790. if (rStart > 0) {
  1791. polygons.push(new CSG.Polygon([start, point(0, t0, rStart), point(0, t1, rStart)]));
  1792. polygons.push(new CSG.Polygon([point(0, t0, rStart), point(1, t0, rEnd), point(0, t1, rStart)]));
  1793. }
  1794. if (rEnd > 0) {
  1795. polygons.push(new CSG.Polygon([end, point(1, t1, rEnd), point(1, t0, rEnd)]));
  1796. polygons.push(new CSG.Polygon([point(1, t0, rEnd), point(1, t1, rEnd), point(0, t1, rStart)]));
  1797. }
  1798. }
  1799. }
  1800. if (alpha < 360) {
  1801. polygons.push(new CSG.Polygon([start, end, point(0, 0, rStart)]));
  1802. polygons.push(new CSG.Polygon([point(0, 0, rStart), end, point(1, 0, rEnd)]));
  1803. polygons.push(new CSG.Polygon([start, point(0, 1, rStart), end]));
  1804. polygons.push(new CSG.Polygon([point(0, 1, rStart), point(1, 1, rEnd), end]));
  1805. }
  1806. }
  1807. var result = CSG.fromPolygons(polygons);
  1808. result.properties.cylinder = new CSG.Properties();
  1809. result.properties.cylinder.start = new CSG.Connector(s, axisZ.negated(), axisX);
  1810. result.properties.cylinder.end = new CSG.Connector(e, axisZ, axisX);
  1811. var cylCenter = s.plus(ray.times(0.5));
  1812. var fptVec = axisX.rotate(s, axisZ, -alpha / 2).times((rStart + rEnd) / 2);
  1813. var fptVec90 = fptVec.cross(axisZ);
  1814. // note this one is NOT a face normal for a cone. - It's horizontal from cyl perspective
  1815. result.properties.cylinder.facepointH = new CSG.Connector(cylCenter.plus(fptVec), fptVec, axisZ);
  1816. result.properties.cylinder.facepointH90 = new CSG.Connector(cylCenter.plus(fptVec90), fptVec90, axisZ);
  1817. return result;
  1818. };
  1819. // Like a cylinder, but with rounded ends instead of flat
  1820. //
  1821. // Parameters:
  1822. // start: start point of cylinder (default [0, -1, 0])
  1823. // end: end point of cylinder (default [0, 1, 0])
  1824. // radius: radius of cylinder (default 1), must be a scalar
  1825. // resolution: determines the number of polygons per 360 degree revolution (default 12)
  1826. // normal: a vector determining the starting angle for tesselation. Should be non-parallel to start.minus(end)
  1827. //
  1828. // Example usage:
  1829. //
  1830. // var cylinder = CSG.roundedCylinder({
  1831. // start: [0, -1, 0],
  1832. // end: [0, 1, 0],
  1833. // radius: 1,
  1834. // resolution: 16
  1835. // });
  1836. CSG.roundedCylinder = function(options) {
  1837. var p1 = CSG.parseOptionAs3DVector(options, "start", [0, -1, 0]);
  1838. var p2 = CSG.parseOptionAs3DVector(options, "end", [0, 1, 0]);
  1839. var radius = CSG.parseOptionAsFloat(options, "radius", 1);
  1840. var direction = p2.minus(p1);
  1841. var defaultnormal;
  1842. if (Math.abs(direction.x) > Math.abs(direction.y)) {
  1843. defaultnormal = new CSG.Vector3D(0, 1, 0);
  1844. } else {
  1845. defaultnormal = new CSG.Vector3D(1, 0, 0);
  1846. }
  1847. var normal = CSG.parseOptionAs3DVector(options, "normal", defaultnormal);
  1848. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution3D);
  1849. if (resolution < 4) resolution = 4;
  1850. var polygons = [];
  1851. var qresolution = Math.floor(0.25 * resolution);
  1852. var length = direction.length();
  1853. if (length < 1e-10) {
  1854. return CSG.sphere({
  1855. center: p1,
  1856. radius: radius,
  1857. resolution: resolution
  1858. });
  1859. }
  1860. var zvector = direction.unit().times(radius);
  1861. var xvector = zvector.cross(normal).unit().times(radius);
  1862. var yvector = xvector.cross(zvector).unit().times(radius);
  1863. var prevcylinderpoint;
  1864. for (var slice1 = 0; slice1 <= resolution; slice1++) {
  1865. var angle = Math.PI * 2.0 * slice1 / resolution;
  1866. var cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle)));
  1867. if (slice1 > 0) {
  1868. // cylinder vertices:
  1869. var vertices = [];
  1870. vertices.push(new CSG.Vertex(p1.plus(cylinderpoint)));
  1871. vertices.push(new CSG.Vertex(p1.plus(prevcylinderpoint)));
  1872. vertices.push(new CSG.Vertex(p2.plus(prevcylinderpoint)));
  1873. vertices.push(new CSG.Vertex(p2.plus(cylinderpoint)));
  1874. polygons.push(new CSG.Polygon(vertices));
  1875. var prevcospitch, prevsinpitch;
  1876. for (var slice2 = 0; slice2 <= qresolution; slice2++) {
  1877. var pitch = 0.5 * Math.PI * slice2 / qresolution;
  1878. //var pitch = Math.asin(slice2/qresolution);
  1879. var cospitch = Math.cos(pitch);
  1880. var sinpitch = Math.sin(pitch);
  1881. if (slice2 > 0) {
  1882. vertices = [];
  1883. vertices.push(new CSG.Vertex(p1.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))));
  1884. vertices.push(new CSG.Vertex(p1.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch)))));
  1885. if (slice2 < qresolution) {
  1886. vertices.push(new CSG.Vertex(p1.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))));
  1887. }
  1888. vertices.push(new CSG.Vertex(p1.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch)))));
  1889. polygons.push(new CSG.Polygon(vertices));
  1890. vertices = [];
  1891. vertices.push(new CSG.Vertex(p2.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))));
  1892. vertices.push(new CSG.Vertex(p2.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch)))));
  1893. if (slice2 < qresolution) {
  1894. vertices.push(new CSG.Vertex(p2.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))));
  1895. }
  1896. vertices.push(new CSG.Vertex(p2.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch)))));
  1897. vertices.reverse();
  1898. polygons.push(new CSG.Polygon(vertices));
  1899. }
  1900. prevcospitch = cospitch;
  1901. prevsinpitch = sinpitch;
  1902. }
  1903. }
  1904. prevcylinderpoint = cylinderpoint;
  1905. }
  1906. var result = CSG.fromPolygons(polygons);
  1907. var ray = zvector.unit();
  1908. var axisX = xvector.unit();
  1909. result.properties.roundedCylinder = new CSG.Properties();
  1910. result.properties.roundedCylinder.start = new CSG.Connector(p1, ray.negated(), axisX);
  1911. result.properties.roundedCylinder.end = new CSG.Connector(p2, ray, axisX);
  1912. result.properties.roundedCylinder.facepoint = p1.plus(xvector);
  1913. return result;
  1914. };
  1915. // Construct an axis-aligned solid rounded cuboid.
  1916. // Parameters:
  1917. // center: center of cube (default [0,0,0])
  1918. // radius: radius of cube (default [1,1,1]), can be specified as scalar or as 3D vector
  1919. // roundradius: radius of rounded corners (default 0.2), must be a scalar
  1920. // resolution: determines the number of polygons per 360 degree revolution (default 8)
  1921. //
  1922. // Example code:
  1923. //
  1924. // var cube = CSG.roundedCube({
  1925. // center: [0, 0, 0],
  1926. // radius: 1,
  1927. // roundradius: 0.2,
  1928. // resolution: 8,
  1929. // });
  1930. CSG.roundedCube = function(options) {
  1931. var EPS = 1e-5;
  1932. var minRR = 1e-2; //minroundradius 1e-3 gives rounding errors already
  1933. var center, cuberadius;
  1934. options = options || {};
  1935. if (('corner1' in options) || ('corner2' in options)) {
  1936. if (('center' in options) || ('radius' in options)) {
  1937. throw new Error("roundedCube: should either give a radius and center parameter, or a corner1 and corner2 parameter");
  1938. }
  1939. corner1 = CSG.parseOptionAs3DVector(options, "corner1", [0, 0, 0]);
  1940. corner2 = CSG.parseOptionAs3DVector(options, "corner2", [1, 1, 1]);
  1941. center = corner1.plus(corner2).times(0.5);
  1942. cuberadius = corner2.minus(corner1).times(0.5);
  1943. } else {
  1944. center = CSG.parseOptionAs3DVector(options, "center", [0, 0, 0]);
  1945. cuberadius = CSG.parseOptionAs3DVector(options, "radius", [1, 1, 1]);
  1946. }
  1947. cuberadius = cuberadius.abs(); // negative radii make no sense
  1948. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution3D);
  1949. if (resolution < 4) resolution = 4;
  1950. if (resolution%2 == 1 && resolution < 8) resolution = 8; // avoid ugly
  1951. var roundradius = CSG.parseOptionAs3DVector(options, "roundradius", [0.2, 0.2, 0.2]);
  1952. // slight hack for now - total radius stays ok
  1953. roundradius = CSG.Vector3D.Create(Math.max(roundradius.x, minRR), Math.max(roundradius.y, minRR), Math.max(roundradius.z, minRR));
  1954. var innerradius = cuberadius.minus(roundradius);
  1955. if (innerradius.x < 0 || innerradius.y < 0 || innerradius.z < 0) {
  1956. throw('roundradius <= radius!');
  1957. }
  1958. var res = CSG.sphere({radius:1, resolution:resolution});
  1959. res = res.scale(roundradius);
  1960. innerradius.x > EPS && (res = res.stretchAtPlane([1, 0, 0], [0, 0, 0], 2*innerradius.x));
  1961. innerradius.y > EPS && (res = res.stretchAtPlane([0, 1, 0], [0, 0, 0], 2*innerradius.y));
  1962. innerradius.z > EPS && (res = res.stretchAtPlane([0, 0, 1], [0, 0, 0], 2*innerradius.z));
  1963. res = res.tr([-innerradius.x+center.x, -innerradius.y+center.y, -innerradius.z+center.z]);
  1964. res = res.reTesselated();
  1965. res.properties.roundedCube = new CSG.Properties();
  1966. res.properties.roundedCube.center = new CSG.Vertex(center);
  1967. res.properties.roundedCube.facecenters = [
  1968. new CSG.Connector(new CSG.Vector3D([cuberadius.x, 0, 0]).plus(center), [1, 0, 0], [0, 0, 1]),
  1969. new CSG.Connector(new CSG.Vector3D([-cuberadius.x, 0, 0]).plus(center), [-1, 0, 0], [0, 0, 1]),
  1970. new CSG.Connector(new CSG.Vector3D([0, cuberadius.y, 0]).plus(center), [0, 1, 0], [0, 0, 1]),
  1971. new CSG.Connector(new CSG.Vector3D([0, -cuberadius.y, 0]).plus(center), [0, -1, 0], [0, 0, 1]),
  1972. new CSG.Connector(new CSG.Vector3D([0, 0, cuberadius.z]).plus(center), [0, 0, 1], [1, 0, 0]),
  1973. new CSG.Connector(new CSG.Vector3D([0, 0, -cuberadius.z]).plus(center), [0, 0, -1], [1, 0, 0])
  1974. ];
  1975. return res;
  1976. };
  1977. /**
  1978. * polyhedron accepts openscad style arguments. I.e. define face vertices clockwise looking from outside
  1979. */
  1980. CSG.polyhedron = function(options) {
  1981. options = options || {};
  1982. if (('points' in options) !== ('faces' in options)) {
  1983. throw new Error("polyhedron needs 'points' and 'faces' arrays");
  1984. }
  1985. var vertices = CSG.parseOptionAs3DVectorList(options, "points", [
  1986. [1, 1, 0],
  1987. [1, -1, 0],
  1988. [-1, -1, 0],
  1989. [-1, 1, 0],
  1990. [0, 0, 1]
  1991. ])
  1992. .map(function(pt) {
  1993. return new CSG.Vertex(pt);
  1994. });
  1995. var faces = CSG.parseOption(options, "faces", [
  1996. [0, 1, 4],
  1997. [1, 2, 4],
  1998. [2, 3, 4],
  1999. [3, 0, 4],
  2000. [1, 0, 3],
  2001. [2, 1, 3]
  2002. ]);
  2003. // openscad convention defines inward normals - so we have to invert here
  2004. faces.forEach(function(face) {
  2005. face.reverse();
  2006. });
  2007. var polygons = faces.map(function(face) {
  2008. return new CSG.Polygon(face.map(function(idx) {
  2009. return vertices[idx];
  2010. }));
  2011. });
  2012. // TODO: facecenters as connectors? probably overkill. Maybe centroid
  2013. // the re-tesselation here happens because it's so easy for a user to
  2014. // create parametrized polyhedrons that end up with 1-2 dimensional polygons.
  2015. // These will create infinite loops at CSG.Tree()
  2016. return CSG.fromPolygons(polygons).reTesselated();
  2017. };
  2018. CSG.IsFloat = function(n) {
  2019. return (!isNaN(n)) || (n === Infinity) || (n === -Infinity);
  2020. };
  2021. // solve 2x2 linear equation:
  2022. // [ab][x] = [u]
  2023. // [cd][y] [v]
  2024. CSG.solve2Linear = function(a, b, c, d, u, v) {
  2025. var det = a * d - b * c;
  2026. var invdet = 1.0 / det;
  2027. var x = u * d - b * v;
  2028. var y = -u * c + a * v;
  2029. x *= invdet;
  2030. y *= invdet;
  2031. return [x, y];
  2032. };
  2033. // # class Vector3D
  2034. // Represents a 3D vector.
  2035. //
  2036. // Example usage:
  2037. //
  2038. // new CSG.Vector3D(1, 2, 3);
  2039. // new CSG.Vector3D([1, 2, 3]);
  2040. // new CSG.Vector3D({ x: 1, y: 2, z: 3 });
  2041. // new CSG.Vector3D(1, 2); // assumes z=0
  2042. // new CSG.Vector3D([1, 2]); // assumes z=0
  2043. CSG.Vector3D = function(x, y, z) {
  2044. if (arguments.length == 3) {
  2045. this._x = parseFloat(x);
  2046. this._y = parseFloat(y);
  2047. this._z = parseFloat(z);
  2048. } else if (arguments.length == 2) {
  2049. this._x = parseFloat(x);
  2050. this._y = parseFloat(y);
  2051. this._z = 0;
  2052. } else {
  2053. var ok = true;
  2054. if (arguments.length == 1) {
  2055. if (typeof(x) == "object") {
  2056. if (x instanceof CSG.Vector3D) {
  2057. this._x = x._x;
  2058. this._y = x._y;
  2059. this._z = x._z;
  2060. } else if (x instanceof CSG.Vector2D) {
  2061. this._x = x._x;
  2062. this._y = x._y;
  2063. this._z = 0;
  2064. } else if (x instanceof Array) {
  2065. if ((x.length < 2) || (x.length > 3)) {
  2066. ok = false;
  2067. } else {
  2068. this._x = parseFloat(x[0]);
  2069. this._y = parseFloat(x[1]);
  2070. if (x.length == 3) {
  2071. this._z = parseFloat(x[2]);
  2072. } else {
  2073. this._z = 0;
  2074. }
  2075. }
  2076. } else if (('_x' in x) && ('_y' in x)) {
  2077. this._x = parseFloat(x._x);
  2078. this._y = parseFloat(x._y);
  2079. if ('_z' in x) {
  2080. this._z = parseFloat(x._z);
  2081. } else {
  2082. this._z = 0;
  2083. }
  2084. } else ok = false;
  2085. } else {
  2086. var v = parseFloat(x);
  2087. this._x = v;
  2088. this._y = v;
  2089. this._z = v;
  2090. }
  2091. } else ok = false;
  2092. if (ok) {
  2093. if ((!CSG.IsFloat(this._x)) || (!CSG.IsFloat(this._y)) || (!CSG.IsFloat(this._z))) ok = false;
  2094. }
  2095. if (!ok) {
  2096. throw new Error("wrong arguments");
  2097. }
  2098. }
  2099. };
  2100. // This does the same as new CSG.Vector3D(x,y,z) but it doesn't go through the constructor
  2101. // and the parameters are not validated. Is much faster.
  2102. CSG.Vector3D.Create = function(x, y, z) {
  2103. var result = Object.create(CSG.Vector3D.prototype);
  2104. result._x = x;
  2105. result._y = y;
  2106. result._z = z;
  2107. return result;
  2108. };
  2109. CSG.Vector3D.prototype = {
  2110. get x() {
  2111. return this._x;
  2112. },
  2113. get y() {
  2114. return this._y;
  2115. },
  2116. get z() {
  2117. return this._z;
  2118. },
  2119. set x(v) {
  2120. throw new Error("Vector3D is immutable");
  2121. },
  2122. set y(v) {
  2123. throw new Error("Vector3D is immutable");
  2124. },
  2125. set z(v) {
  2126. throw new Error("Vector3D is immutable");
  2127. },
  2128. clone: function() {
  2129. return CSG.Vector3D.Create(this._x, this._y, this._z);
  2130. },
  2131. negated: function() {
  2132. return CSG.Vector3D.Create(-this._x, -this._y, -this._z);
  2133. },
  2134. abs: function() {
  2135. return CSG.Vector3D.Create(Math.abs(this._x), Math.abs(this._y), Math.abs(this._z));
  2136. },
  2137. plus: function(a) {
  2138. return CSG.Vector3D.Create(this._x + a._x, this._y + a._y, this._z + a._z);
  2139. },
  2140. minus: function(a) {
  2141. return CSG.Vector3D.Create(this._x - a._x, this._y - a._y, this._z - a._z);
  2142. },
  2143. times: function(a) {
  2144. return CSG.Vector3D.Create(this._x * a, this._y * a, this._z * a);
  2145. },
  2146. dividedBy: function(a) {
  2147. return CSG.Vector3D.Create(this._x / a, this._y / a, this._z / a);
  2148. },
  2149. dot: function(a) {
  2150. return this._x * a._x + this._y * a._y + this._z * a._z;
  2151. },
  2152. lerp: function(a, t) {
  2153. return this.plus(a.minus(this).times(t));
  2154. },
  2155. lengthSquared: function() {
  2156. return this.dot(this);
  2157. },
  2158. length: function() {
  2159. return Math.sqrt(this.lengthSquared());
  2160. },
  2161. unit: function() {
  2162. return this.dividedBy(this.length());
  2163. },
  2164. cross: function(a) {
  2165. return CSG.Vector3D.Create(
  2166. this._y * a._z - this._z * a._y, this._z * a._x - this._x * a._z, this._x * a._y - this._y * a._x);
  2167. },
  2168. distanceTo: function(a) {
  2169. return this.minus(a).length();
  2170. },
  2171. distanceToSquared: function(a) {
  2172. return this.minus(a).lengthSquared();
  2173. },
  2174. equals: function(a) {
  2175. return (this._x == a._x) && (this._y == a._y) && (this._z == a._z);
  2176. },
  2177. // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector)
  2178. // Returns a new CSG.Vector3D
  2179. multiply4x4: function(matrix4x4) {
  2180. return matrix4x4.leftMultiply1x3Vector(this);
  2181. },
  2182. transform: function(matrix4x4) {
  2183. return matrix4x4.leftMultiply1x3Vector(this);
  2184. },
  2185. toString: function() {
  2186. return "(" + this._x.toFixed(2) + ", " + this._y.toFixed(2) + ", " + this._z.toFixed(2) + ")";
  2187. },
  2188. // find a vector that is somewhat perpendicular to this one
  2189. randomNonParallelVector: function() {
  2190. var abs = this.abs();
  2191. if ((abs._x <= abs._y) && (abs._x <= abs._z)) {
  2192. return CSG.Vector3D.Create(1, 0, 0);
  2193. } else if ((abs._y <= abs._x) && (abs._y <= abs._z)) {
  2194. return CSG.Vector3D.Create(0, 1, 0);
  2195. } else {
  2196. return CSG.Vector3D.Create(0, 0, 1);
  2197. }
  2198. },
  2199. min: function(p) {
  2200. return CSG.Vector3D.Create(
  2201. Math.min(this._x, p._x), Math.min(this._y, p._y), Math.min(this._z, p._z));
  2202. },
  2203. max: function(p) {
  2204. return CSG.Vector3D.Create(
  2205. Math.max(this._x, p._x), Math.max(this._y, p._y), Math.max(this._z, p._z));
  2206. },
  2207. // functions added to support 3D convex hull
  2208. // J. Yoder, 2015
  2209. // set elements of this vector to 0
  2210. setZero: function() {
  2211. this._x = 0;
  2212. this._y = 0;
  2213. this._z = 0;
  2214. },
  2215. // add second vector to first vector
  2216. hAdd: function(v1,v2) {
  2217. v1._x += v2._x;
  2218. v1._y += v2._y;
  2219. v1._z += v2._z;
  2220. },
  2221. hTimes: function(v1,c) {
  2222. v1._x *= c;
  2223. v1._y *= c;
  2224. v1._z *= c;
  2225. },
  2226. // normalize a vector in place
  2227. normalize: function() {
  2228. // console.log(" in normalize", this);
  2229. var lenSqr = this.lengthSquared();
  2230. var err = lenSqr - 1;
  2231. var DOUBLE_PREC = 2.2204460492503131e-16;
  2232. if (err > (2*DOUBLE_PREC) || err < -(2*DOUBLE_PREC)) {
  2233. // console.log("normalizing");
  2234. var len = Math.sqrt(lenSqr);
  2235. this._x /= len;
  2236. this._y /= len;
  2237. this._z /= len;
  2238. }
  2239. },
  2240. // set a vector given x,y,z values
  2241. set: function(x,y,z) {
  2242. this._x = x;
  2243. this._y = y;
  2244. this._z = z;
  2245. }
  2246. };
  2247. // # class Vertex
  2248. // Represents a vertex of a polygon. Use your own vertex class instead of this
  2249. // one to provide additional features like texture coordinates and vertex
  2250. // colors. Custom vertex classes need to provide a `pos` property
  2251. // `flipped()`, and `interpolate()` methods that behave analogous to the ones
  2252. // defined by `CSG.Vertex`.
  2253. CSG.Vertex = function(pos) {
  2254. this.pos = pos;
  2255. };
  2256. // create from an untyped object with identical property names:
  2257. CSG.Vertex.fromObject = function(obj) {
  2258. var pos = new CSG.Vector3D(obj.pos);
  2259. return new CSG.Vertex(pos);
  2260. };
  2261. CSG.Vertex.prototype = {
  2262. // Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the
  2263. // orientation of a polygon is flipped.
  2264. flipped: function() {
  2265. return this;
  2266. },
  2267. getTag: function() {
  2268. var result = this.tag;
  2269. if (!result) {
  2270. result = CSG.getTag();
  2271. this.tag = result;
  2272. }
  2273. return result;
  2274. },
  2275. // Create a new vertex between this vertex and `other` by linearly
  2276. // interpolating all properties using a parameter of `t`. Subclasses should
  2277. // override this to interpolate additional properties.
  2278. interpolate: function(other, t) {
  2279. var newpos = this.pos.lerp(other.pos, t);
  2280. return new CSG.Vertex(newpos);
  2281. },
  2282. // Affine transformation of vertex. Returns a new CSG.Vertex
  2283. transform: function(matrix4x4) {
  2284. var newpos = this.pos.multiply4x4(matrix4x4);
  2285. return new CSG.Vertex(newpos);
  2286. },
  2287. toString: function() {
  2288. return this.pos.toString();
  2289. }
  2290. };
  2291. // 3D vertex used in 3D hull
  2292. // needs to hold a vector3D point pnt, an integer index, next and prev. vertices, and face info.
  2293. CSG.hVertex = function(x,y,z,idx) {
  2294. this.pnt = new CSG.Vector3D(x,y,z);
  2295. if (arguments.length == 4)
  2296. this.index = idx;
  2297. this.next = null;
  2298. this.prev = null;
  2299. this.face = null;
  2300. }
  2301. CSG.hVertex.prototype = {
  2302. clone: function() {
  2303. return new CSG.hVertex(this._x,this._y,this._z,this.index);
  2304. }
  2305. }
  2306. // doubly linked list of vertices. Store a head and a tail pointer
  2307. // used for 3D hull. - JY
  2308. CSG.hVertexList = function() {
  2309. this.head = null;
  2310. this.tail = null;
  2311. }
  2312. CSG.hVertexList.prototype = {
  2313. // clear the list
  2314. clear: function() {
  2315. this.head = null;
  2316. this.tail = null;
  2317. },
  2318. // add a vertex to the end of the list
  2319. // assumes that the vertex to be added is already an instantiated hVertex object
  2320. add: function(v) {
  2321. if (this.head == null)
  2322. this.head = v;
  2323. else
  2324. this.tail.next = v;
  2325. v.prev = this.tail;
  2326. v.next = null;
  2327. this.tail = v;
  2328. },
  2329. // Add a chain of vertices to the end of this list.
  2330. addAll: function(vtx) {
  2331. if (this.head == null)
  2332. this.head = vtx;
  2333. else
  2334. this.tail.next = vtx;
  2335. vtx.prev = this.tail;
  2336. while (vtx.next != null) {
  2337. vtx = vtx.next;
  2338. }
  2339. this.tail = vtx;
  2340. },
  2341. //Delete a vertex or vertex chain from this list
  2342. delete: function(vtx1, vtx2) {
  2343. // delete single vertex
  2344. if (arguments.length == 1) {
  2345. if (vtx1.prev == null)
  2346. this.head = vtx1.next;
  2347. else
  2348. vtx1.prev.next = vtx1.next;
  2349. if (vtx1.next == null)
  2350. this.tail = vtx1.prev;
  2351. else
  2352. vtx1.next.prev = vtx1.prev;
  2353. }
  2354. // delete chain of contiguous vertices with vtx1 before vtx2
  2355. else if (arguments.length == 2) {
  2356. if (vtx1.prev == null)
  2357. this.head = vtx2.next;
  2358. else
  2359. vtx1.prev.next = vtx2.next;
  2360. if (vtx2.next == null)
  2361. this.tail = vtx1.prev;
  2362. else
  2363. vtx2.next.prev = vtx1.prev;
  2364. }
  2365. },
  2366. // insert a vertex into the list before another given vertex
  2367. insertBefore: function(vtx, next) {
  2368. vtx.prev = next.prev;
  2369. if (next.prev == null)
  2370. this.head = vtx;
  2371. else
  2372. next.prev.next = vtx;
  2373. vtx.next = next;
  2374. next.prev = vtx;
  2375. },
  2376. // return the first vertex in the list
  2377. first: function() {
  2378. return this.head;
  2379. },
  2380. // return true if the list is empty
  2381. isEmpty: function() {
  2382. return this.head == null;
  2383. }
  2384. } // end hVertexList
  2385. // HalfEdge class for 3D hull
  2386. // represents the half edges that surround each face in a counter-clockwise direction
  2387. CSG.HalfEdge = function(v, f) {
  2388. if (arguments.length == 2) {
  2389. // the vertex associated with the head of this half-edge
  2390. this.vertex = v;
  2391. // triangular face associated with this half-edge
  2392. this.face = f;
  2393. }
  2394. else {
  2395. this.vertex = null;
  2396. this.face = null;
  2397. }
  2398. // list pointers
  2399. this.prev = null;
  2400. this.next = null;
  2401. // half-edge associated with the opposite triangle
  2402. // adjacent to this edge
  2403. this.opposite = null;
  2404. }
  2405. CSG.HalfEdge.prototype = {
  2406. // set the value of the next edge adjacent
  2407. // counter clockwise to this one within the triangle
  2408. // edge parameter is the next adjacent edge
  2409. setNext: function(edge) {
  2410. this.next = edge;
  2411. },
  2412. // get the value of the next adjacent edge
  2413. // counter clockwise to this one in the triangle
  2414. getNext: function() {
  2415. return this.next;
  2416. },
  2417. //set the value of the previous edge (clockwise)
  2418. setPrev: function(edge) {
  2419. this.prev = edge;
  2420. },
  2421. // get the value of the previous edge (clockwise)
  2422. getPrev: function() {
  2423. return this.prev;
  2424. },
  2425. // returns the triangular face located to the left of this half-edge
  2426. getFace: function() {
  2427. return this.face;
  2428. },
  2429. // returns the half-edge opposite to this half-edge
  2430. getOpposite: function() {
  2431. return this.opposite;
  2432. },
  2433. // sets the half-edge opposite to this half-edge
  2434. // edge param is a half-edge
  2435. setOpposite: function(edge) {
  2436. this.opposite = edge;
  2437. edge.opposite = this;
  2438. },
  2439. // returns the head vertex associated with this half-edge
  2440. head: function() {
  2441. return this.vertex;
  2442. },
  2443. // returns the tail vertex associated with this half-edge
  2444. tail: function() {
  2445. return (this.prev != null) ? this.prev.vertex : null;
  2446. },
  2447. // returns the opposite triangular face associated with this half-edge
  2448. oppositeFace: function() {
  2449. return (this.opposite != null) ? this.opposite.face : null;
  2450. },
  2451. // produces a string of this half edge by the point index values
  2452. // of its head and tail vertices
  2453. getVertexString: function() {
  2454. if (this.tail() != null)
  2455. return "" + this.tail().index + "-" + this.head().index;
  2456. else
  2457. return "? -" + this.head().index;
  2458. },
  2459. // returns the length of this half-edge
  2460. length: function() {
  2461. if (this.tail() != null)
  2462. return this.head().pnt.distanceTo(this.tail().pnt);
  2463. else
  2464. return -1;
  2465. },
  2466. // returns the length squared of this half-edge
  2467. lengthSquared: function() {
  2468. if (this.tail() != null)
  2469. return this.head().pnt.distanceToSquared(this.tail().pnt);
  2470. else
  2471. return -1;
  2472. }
  2473. } // end HalfEdge class
  2474. // class Face
  2475. // Basic triangular face to form the convex 3D hull
  2476. // A face has a planar normal, a planar offset, and a
  2477. // doubly linked list of three HalfEdges which surround
  2478. // the face in a counter-clockwise direction.
  2479. CSG.Face = function() {
  2480. this.normal = new CSG.Vector3D(0,0,0);
  2481. this.centroid = new CSG.Vector3D(0,0,0);
  2482. this.mark = 1; // VISIBLE
  2483. // list of half-edges
  2484. this.he0 = null;
  2485. // area of the face
  2486. this.area = -1;
  2487. // planar offset
  2488. this.planeOffset = -1;
  2489. this.index = -1;
  2490. this.numVerts = -1;
  2491. // Faces are kept in a list
  2492. this.next = null;
  2493. // List of outside vertices?
  2494. this.outside = null;
  2495. }
  2496. CSG.Face.prototype = {
  2497. computeCentroid: function(centroid) {
  2498. // console.log("centroid was:",centroid);
  2499. centroid.setZero();
  2500. var he = this.he0;
  2501. // console.log("he",he);
  2502. do {
  2503. // console.log("now centroid is:",centroid);
  2504. centroid.hAdd(centroid, he.head().pnt);
  2505. he = he.next;
  2506. }
  2507. while (he != this.he0);
  2508. centroid.hTimes(centroid, 1 / this.numVerts);
  2509. // console.log("now centroid is (done):",centroid);
  2510. },
  2511. computeNormal: function(normal, minArea) {
  2512. var he1 = this.he0.next;
  2513. var he2 = he1.next;
  2514. var p0 = this.he0.head().pnt;
  2515. var p2 = he1.head().pnt;
  2516. var d2x = p2._x - p0._x;
  2517. var d2y = p2._y - p0._y;
  2518. var d2z = p2._z - p0._z;
  2519. this.normal.setZero();
  2520. this.numVerts = 2;
  2521. while (he2 != this.he0) {
  2522. var d1x = d2x;
  2523. var d1y = d2y;
  2524. var d1z = d2z;
  2525. p2 = he2.head().pnt;
  2526. d2x = p2._x - p0._x;
  2527. d2y = p2._y - p0._y;
  2528. d2z = p2._z - p0._z;
  2529. this.normal._x += d1y*d2z - d1z*d2y;
  2530. this.normal._y += d1z*d2x - d1x*d2z;
  2531. this.normal._z += d1x*d2y - d1y*d2x;
  2532. he1 = he2;
  2533. he2 = he2.next;
  2534. this.numVerts++;
  2535. }
  2536. this.area = this.normal.length();
  2537. this.normal.hTimes(this.normal,1/this.area);
  2538. if (arguments.length == 2) {
  2539. if (this.area < minArea) {
  2540. // make the normal more robust by removing components parallel to the longest edge
  2541. var hedgeMax = null;
  2542. var lenSqrMax = 0;
  2543. var hedge = this.he0;
  2544. do {
  2545. var lenSqr = hedge.lengthSquared();
  2546. if (lenSqr > lenSqrMax) {
  2547. hedgeMax = hedge;
  2548. lenSqrMax = lenSqr;
  2549. }
  2550. }
  2551. while (hedge != this.he0);
  2552. p2 = hedgeMax.head().pnt;
  2553. p1 = hedgeMax.tail().pnt;
  2554. var lenMax = Math.sqrt(lenSqrMax);
  2555. var ux = (p2._x - p1._x)/lenMax;
  2556. var uy = (p2._y - p1._y)/lenMax;
  2557. var uz = (p2._z - p1._z)/lenMax;
  2558. var dot = this.normal._x*ux + this.normal._y*uy + this.normal._z*uz;
  2559. this.normal._x -= dot*ux;
  2560. this.normal._y -= dot*uy;
  2561. this.normal._z -= dot*uz;
  2562. this.normal.normalize();
  2563. }
  2564. }
  2565. },
  2566. computeNormalAndCentroid: function(minArea) {
  2567. if (arguments.length == 1)
  2568. this.computeNormal(this.normal, minArea);
  2569. else this.computeNormal(this.normal);
  2570. this.computeCentroid(this.centroid);
  2571. this.planeOffset = this.normal.dot(this.centroid);
  2572. },
  2573. // createTriangle creates and returns a triangle
  2574. // using vertices v0, v1, v2. minArea is optional (set to 0 if not given).
  2575. createTriangle: function(v0,v1,v2,minArea) {
  2576. var face = new CSG.Face();
  2577. var he0 = new CSG.HalfEdge (v0, face);
  2578. var he1 = new CSG.HalfEdge (v1, face);
  2579. var he2 = new CSG.HalfEdge (v2, face);
  2580. he0.prev = he2;
  2581. he0.next = he1;
  2582. he1.prev = he0;
  2583. he1.next = he2;
  2584. he2.prev = he1;
  2585. he2.next = he0;
  2586. face.he0 = he0;
  2587. // compute the normal and offset
  2588. if (minArea)
  2589. face.computeNormalAndCentroid(minArea);
  2590. else
  2591. face.computeNormalAndCentroid(0);
  2592. return face;
  2593. },
  2594. // create a face from an array of vertices and an array of indices
  2595. create: function(vtxArray, indices) {
  2596. var face = new CSG.Face();
  2597. var hePrev = null;
  2598. for (var i = 0; i < indices.length; i++) {
  2599. var he = new CSG.HalfEdge(vtxArray[indices[i]], face);
  2600. if (hePrev != null) {
  2601. he.setPrev(hePrev);
  2602. hePrev.setNext(he);
  2603. }
  2604. else {
  2605. face.he0 = he;
  2606. }
  2607. hePrev = he;
  2608. }
  2609. face.he0.setPrev (hePrev);
  2610. hePrev.setNext (face.he0);
  2611. // compute the normal and offset
  2612. face.computeNormalAndCentroid();
  2613. return face;
  2614. },
  2615. // get the i-th half-edge associated with the face.
  2616. // takes an index i (should be between 0 and 2)
  2617. // returns the half-edge.
  2618. getEdge: function(i) {
  2619. var he = this.he0;
  2620. while (i > 0) {
  2621. he = he.next;
  2622. i--;
  2623. }
  2624. while (i < 0) {
  2625. he = he.prev;
  2626. i++;
  2627. }
  2628. return he;
  2629. },
  2630. getFirstEdge: function() {
  2631. return this.he0;
  2632. },
  2633. // finds the half-edge within this face which has tail vt and head vh.
  2634. // takes two vertices (vt, vh)
  2635. // return the half-edge if found, or null.
  2636. findEdge: function(vt, vh) {
  2637. var he = this.he0;
  2638. do {
  2639. if (he.head() == vh && he.tail() == vt)
  2640. return he;
  2641. he = he.next;
  2642. }
  2643. while (he != this.he0);
  2644. return null;
  2645. },
  2646. // calculates the distance from this face to a point p
  2647. distanceToPlane: function(p) {
  2648. return this.normal._x * p._x + this.normal._y * p._y + this.normal._z * p._z - this.planeOffset;
  2649. },
  2650. // returns the normal of the plane associated with this face
  2651. getNormal: function() {
  2652. return this.normal;
  2653. },
  2654. getCentroid: function() {
  2655. return this.centroid;
  2656. },
  2657. numVertices: function() {
  2658. return this.numVerts;
  2659. },
  2660. getVertexString: function() {
  2661. var s = '';
  2662. var he = this.he0;
  2663. do {
  2664. if (s.length == 0)
  2665. s += he.head().index;
  2666. else
  2667. s += " " + he.head().index;
  2668. he = he.next;
  2669. }
  2670. while (he != this.he0);
  2671. return s;
  2672. },
  2673. getVertexIndices: function(idxs) {
  2674. var he = this.he0;
  2675. var i = 0;
  2676. do {
  2677. idxs[i++] = he.head().index;
  2678. he = he.next;
  2679. }
  2680. while (he != this.he0);
  2681. },
  2682. connectHalfEdges: function(hedgePrev, hedge) {
  2683. var discardedFace = null;
  2684. if (hedgePrev.oppositeFace() == hedge.oppositeFace()) {
  2685. // there is a redundant edge we can get rid of
  2686. var oppFace = hedge.oppositeFace();
  2687. var hedgeOpp;
  2688. if (hedgePrev == this.he0)
  2689. this.he0 = hedge;
  2690. if (oppFace.numVertices() == 3) {
  2691. // we can get rid of the opposite face altogether
  2692. hedgeOpp = hedge.getOpposite().prev.getOpposite();
  2693. oppFace.mark = 3; // DELETED
  2694. discardedFace = oppFace;
  2695. }
  2696. else {
  2697. hedgeOpp = hedge.getOpposite().next;
  2698. if (oppFace.he0 == hedgeOpp.prev)
  2699. oppFace.he0 = hedgeOpp;
  2700. hedgeOpp.prev = hedgeOpp.prev.prev;
  2701. hedgeOpp.prev.next = hedgeOpp;
  2702. }
  2703. hedge.prev = hedgePrev.prev;
  2704. hedge.prev.next = hedge;
  2705. hedge.opposite = hedgeOpp;
  2706. hedgeOpp.opposite = hedge;
  2707. // oppFace was modified, so need to recompute
  2708. oppFace.computeNormalAndCentroid();
  2709. }
  2710. else {
  2711. hedgePrev.next = hedge;
  2712. hedge.prev = hedgePrev;
  2713. }
  2714. return discardedFace;
  2715. },
  2716. checkConsistency: function() {
  2717. // do a sanity check on the face
  2718. var hedge = this.he0;
  2719. var maxd = 0;
  2720. var numv = 0;
  2721. if (this.numVerts < 3)
  2722. throw("face" + this.getVertexString() + ": " + "unreflected half edge " + hedge.getVertexString());
  2723. do {
  2724. var hedgeOpp = hedge.getOpposite();
  2725. if (hedgeOpp == null)
  2726. throw("face " + this.getVertexString() + ": " + "unreflected half edge " + hedge.getVertexString());
  2727. else if (hedgeOpp.getOpposite() != hedge)
  2728. throw("face " + this.getVertexString() + ": " + "opposite half edge " + hedgeOpp.getVertexString()
  2729. + " has opposite " + hedgeOpp.getOpposite().getVertexString());
  2730. if (hedgeOpp.head() != hedge.tail() || hedge.head() != hedgeOpp.tail())
  2731. throw("face " + this.getVertexString() + ": " + "half edge " + hedge.getVertexString() +
  2732. " reflected by " + hedgeOpp.getVertexString());
  2733. var oppFace = hedgeOpp.face;
  2734. if (oppFace == null)
  2735. throw("face " + this.getVertexString() + ": " + "no face on half edge " +
  2736. hedgeOpp.getVertexString());
  2737. else if (oppFace.mark == 3) // DELETED
  2738. throw("face " + this.getVertexString() + ": " + "opposite face " +
  2739. oppFace.getVertexString() + " not on hull");
  2740. var d = Math.abs(this.distanceToPlane(hedge.head().pnt));
  2741. if (d > maxd)
  2742. maxd = d;
  2743. numv++;
  2744. hedge = hedge.next;
  2745. }
  2746. while (hedge != this.he0);
  2747. if (numv != this.numVerts)
  2748. throw("face " + this.getVertexString() + " numVerts=" + this.numVerts + " should be " + numv);
  2749. },
  2750. // merges adjacent faces.
  2751. // hedgeAdj: a halfEdge
  2752. // discarded: an array of faces
  2753. mergeAdjacentFace: function(hedgeAdj, discarded) {
  2754. var oppFace = hedgeAdj.oppositeFace();
  2755. var numDiscarded = 0;
  2756. discarded[numDiscarded++] = oppFace;
  2757. oppFace.mark = 3; // DELETED
  2758. var hedgeOpp = hedgeAdj.getOpposite();
  2759. var hedgeAdjPrev = hedgeAdj.prev;
  2760. var hedgeAdjNext = hedgeAdj.next;
  2761. var hedgeOppPrev = hedgeOpp.prev;
  2762. var hedgeOppNext = hedgeOpp.next;
  2763. while (hedgeAdjPrev.oppositeFace() == oppFace) {
  2764. hedgeAdjPrev = hedgeAdjPrev.prev;
  2765. hedgeOppNext = hedgeOppNext.next;
  2766. }
  2767. while (hedgeAdjNext.oppositeFace() == oppFace) {
  2768. hedgeOppPrev = hedgeOppPrev.prev;
  2769. hedgeAdjNext = hedgeAdjNext.next;
  2770. }
  2771. var hedge;
  2772. for (hedge=hedgeOppNext; hedge!=hedgeOppPrev.next; hedge=hedge.next) {
  2773. hedge.face = this;
  2774. }
  2775. if (hedgeAdj == this.he0)
  2776. this.he0 = hedgeAdjNext;
  2777. // handle the half edges at the head
  2778. var discardedFace;
  2779. discardedFace = this.connectHalfEdges(hedgeOppPrev, hedgeAdjNext);
  2780. if (discardedFace != null)
  2781. discarded[numDiscarded++] = discardedFace;
  2782. // handle the half edges at the tail
  2783. discardedFace = this.connectHalfEdges(hedgeAdjPrev, hedgeOppNext);
  2784. if (discardedFace != null)
  2785. discarded[numDiscarded++] = discardedFace;
  2786. this.computeNormalAndCentroid();
  2787. this.checkConsistency();
  2788. return numDiscarded;
  2789. },
  2790. // return the squared area of the triangle defined by
  2791. // the half edge hedge0 and the point at the head of hedge1
  2792. areaSquared: function(hedge0, hedge1) {
  2793. var p0 = hedge0.tail().pnt;
  2794. var p1 = hedge0.head().pnt;
  2795. var p2 = hedge1.head().pnt;
  2796. var dx1 = p1._x - p0._x;
  2797. var dy1 = p1._y - p0._y;
  2798. var dz1 = p1._z - p0._z;
  2799. var dx2 = p2._x - p0._x;
  2800. var dy2 = p2._y - p0._y;
  2801. var dz2 = p2._z - p0._z;
  2802. var x = dy1*dz2 - dz1*dy2;
  2803. var y = dz1*dx2 - dx1*dz2;
  2804. var z = dx1*dy2 - dy1*dx2;
  2805. return x*x + y*y + z*z;
  2806. },
  2807. triangulate: function(newFaces, minArea) {
  2808. var hedge;
  2809. if (this.numVertices < 4) // nothing to triangulate!
  2810. return;
  2811. var v0 = this.he0.head();
  2812. var prevFace = null;
  2813. hedge = this.he0.next;
  2814. var oppPrev = hedge.opposite;
  2815. var face0 = null;
  2816. for (hedge=hedge.next; hedge!=this.he0.prev; hedge=hedge.next) {
  2817. var face = createTriangle (v0, hedge.prev.head(), hedge.head(), minArea);
  2818. face.he0.next.setOpposite(oppPrev);
  2819. face.he0.prev.setOpposite(hedge.opposite);
  2820. oppPrev = face.he0;
  2821. newFaces.add(face);
  2822. if (face0 == null)
  2823. face0 = face;
  2824. }
  2825. hedge = new CSG.HalfEdge (this.he0.prev.prev.head(), this);
  2826. hedge.setOpposite (oppPrev);
  2827. hedge.prev = this.he0;
  2828. hedge.prev.next = hedge;
  2829. hedge.next = this.he0.prev;
  2830. hedge.next.prev = hedge;
  2831. computeNormalAndCentroid (minArea);
  2832. checkConsistency();
  2833. for (var face=face0; face!=null; face=face.next)
  2834. face.checkConsistency();
  2835. }
  2836. } // end of Face.prototype
  2837. CSG.FaceList = function() {
  2838. this.head = null;
  2839. this.tail = null;
  2840. }
  2841. CSG.FaceList.prototype = {
  2842. // clear the list
  2843. clear: function() {
  2844. this.head = null;
  2845. this.tail = null;
  2846. },
  2847. // add to the end of this list
  2848. add: function(f) {
  2849. if (this.head == null)
  2850. this.head = f;
  2851. else
  2852. this.tail.next = f;
  2853. f.next = null;
  2854. this.tail = f;
  2855. },
  2856. first: function() {
  2857. return this.head;
  2858. },
  2859. // returns true if the list is empty
  2860. isEmpty: function() {
  2861. return this.head == null;
  2862. }
  2863. } // end FaceList.prototype
  2864. // implementation of the quickhull algorithm
  2865. // based on the original paper by Barber, Dobkin, and Huhdanpaa (1995)
  2866. // ported from the Java library by John Lloyd
  2867. // https://www.cs.ubc.ca/~lloyd/java/quickhull3d.html
  2868. //
  2869. // function to build a 3D convex hull
  2870. // takes an array of CSG.Vector3D values,
  2871. // returns an array of "faces" (indexes into
  2872. // the original vector array)
  2873. CSG.quickHull3D = function() {
  2874. // the distance tolerance should be computed from input points
  2875. this.AUTOMATIC_TOLERANCE = -1;
  2876. this.DOUBLE_PREC = 2.2204460492503131e-16;
  2877. this.findIndex = -1;
  2878. this.debug = true;
  2879. // estimated size of the point set
  2880. this.charLength = 0;
  2881. // will hold an array of vertices
  2882. this.pointBuffer = [];
  2883. this.vertexPointIndices = [];
  2884. this.discardedFaces = [];
  2885. this.maxVtxs = [];
  2886. this.minVtxs = [];
  2887. for (var i = 0; i < 3; i++) {
  2888. this.maxVtxs.push(new CSG.hVertex(0,0,0,i));
  2889. this.minVtxs.push(new CSG.hVertex(0,0,0,i));
  2890. this.discardedFaces.push(new CSG.Face());
  2891. }
  2892. this.faces = [];
  2893. this.horizon = [];
  2894. this.newFaces = new CSG.FaceList();
  2895. this.unclaimed = new CSG.hVertexList();
  2896. this.claimed = new CSG.hVertexList();
  2897. this.numVertices = 0;
  2898. this.numFaces = 0;
  2899. this.numPoints = 0;
  2900. this.explicitTolerance = this.AUTOMATIC_TOLERANCE;
  2901. this.tolerance = 0;
  2902. }
  2903. CSG.quickHull3D.prototype = {
  2904. build: function(points) {
  2905. // test to see if we have enough points to build a hull.
  2906. if (points.length < 4) {
  2907. console.log("cannot build hull - fewer than four points");
  2908. return null;
  2909. }
  2910. this.initBuffers(points, points.length);
  2911. var doneFaces = this.buildHull();
  2912. return doneFaces;
  2913. },
  2914. initBuffers: function(points,nump) {
  2915. this.pointBuffer = [];
  2916. for (var i = 0; i < nump; i++) {
  2917. this.pointBuffer.push(new CSG.hVertex(points[i]._x,points[i]._y,points[i]._z, i));
  2918. this.vertexPointIndices.push(0);
  2919. }
  2920. this.faces = [];
  2921. this.claimed.clear();
  2922. this.numVertices = nump;
  2923. this.numFaces = 0;
  2924. this.numPoints = nump;
  2925. },
  2926. buildHull: function() {
  2927. var cnt = 0;
  2928. var eyeVtx;
  2929. // console.log(this.pointBuffer[0]);
  2930. this.computeMaxAndMin();
  2931. this.createInitialSimplex();
  2932. while ((eyeVtx = this.nextPointToAdd()) != null) {
  2933. // console.log ("eyeVtx is" , eyeVtx);
  2934. this.addPointToHull(eyeVtx);
  2935. cnt++;
  2936. // console.log ("iteration " + cnt + " done");
  2937. }
  2938. this.reindexFacesAndVertices();
  2939. // console.log("hull done");
  2940. var doneFaces = this.getFaces();
  2941. // console.log(doneFaces);
  2942. // var doneVerts = this.getVertices();
  2943. // console.log(doneVerts);
  2944. // console.log("the points:");
  2945. // this.printPoints();
  2946. return(doneFaces);
  2947. },
  2948. computeMaxAndMin: function() {
  2949. // console.log(this.maxVtxs);
  2950. // console.log(this.pointBuffer);
  2951. var pt = this.pointBuffer[0];
  2952. for (var i = 0; i < 3; i++) {
  2953. this.maxVtxs[i] = this.pointBuffer[0];
  2954. this.minVtxs[i] = this.pointBuffer[0];
  2955. }
  2956. // console.log(this.maxVtxs,this.minVtxs);
  2957. var max = [pt.pnt._x, pt.pnt._y, pt.pnt._z];
  2958. var min = [pt.pnt._x, pt.pnt._y, pt.pnt._z];
  2959. for (var i = 0; i < this.numPoints; i++) {
  2960. var pnt = this.pointBuffer[i].pnt;
  2961. if (pnt._x > max[0]) {
  2962. max[0] = pnt._x;
  2963. this.maxVtxs[0] = this.pointBuffer[i];
  2964. }
  2965. else if (pnt._x < min[0]) {
  2966. min[0] = pnt._x;
  2967. this.minVtxs[0] = this.pointBuffer[i];
  2968. }
  2969. // y
  2970. if (pnt._y > max[1]) {
  2971. max[1] = pnt._y;
  2972. this.maxVtxs[1] = this.pointBuffer[i];
  2973. }
  2974. else if (pnt._y < min[1]) {
  2975. min[1] = pnt._y;
  2976. this.minVtxs[1] = this.pointBuffer[i];
  2977. }
  2978. // z
  2979. if (pnt._z > max[2]) {
  2980. max[2] = pnt._z;
  2981. this.maxVtxs[2] = this.pointBuffer[i];
  2982. }
  2983. else if (pnt._z < min[2]) {
  2984. min[2] = pnt._z;
  2985. this.minVtxs[2] = this.pointBuffer[i];
  2986. }
  2987. }
  2988. // epsilon formula is from QuickHull
  2989. this.charLength = Math.max(max[0]-min[0], max[1]-min[1], max[2]-min[2]);
  2990. // console.log("longest delta was: ",this.charLength);
  2991. if (this.explicitTolerance == this.AUTOMATIC_TOLERANCE) {
  2992. this.tolerance =
  2993. 3*this.DOUBLE_PREC*(Math.max(Math.abs(max[0]),Math.abs(min[0]))+
  2994. Math.max(Math.abs(max[1]),Math.abs(min[1]))+
  2995. Math.max(Math.abs(max[2]),Math.abs(min[2])));
  2996. }
  2997. else {
  2998. this.tolerance = this.explicitTolerance;
  2999. }
  3000. // console.log("tolerance: ",this.tolerance);
  3001. // console.log("max and min:", this.maxVtxs, this.minVtxs);
  3002. },
  3003. createInitialSimplex: function() {
  3004. // console.log("in createInitialSimplex");
  3005. var max = 0;
  3006. var imax = 0;
  3007. var dx = this.maxVtxs[0].pnt._x - this.minVtxs[0].pnt._x;
  3008. var dy = this.maxVtxs[1].pnt._y - this.minVtxs[1].pnt._y;
  3009. var dz = this.maxVtxs[2].pnt._z - this.minVtxs[2].pnt._z;
  3010. if (dx > max) {
  3011. max = dx;
  3012. imax = 0;
  3013. }
  3014. if (dy > max) {
  3015. max = dy;
  3016. imax = 1;
  3017. }
  3018. if (dz > max) {
  3019. max = dz;
  3020. imax = 2;
  3021. }
  3022. if (max <= this.tolerance)
  3023. throw("hull points are all coincident - fail!");
  3024. var vtx = [];
  3025. // set the first two points to be those with the greatest
  3026. // one dimensional separation
  3027. vtx[0] = this.maxVtxs[imax];
  3028. vtx[1] = this.minVtxs[imax];
  3029. // console.log("vtx is:",vtx);
  3030. // set the third vertex to be the vertex farthest from
  3031. // the line between vtx0 and vtx1
  3032. var u01 = new CSG.Vector3D(vtx[1].pnt._x,vtx[1].pnt._y,vtx[1].pnt._z);
  3033. u01 = u01.minus(vtx[0].pnt);
  3034. u01.normalize();
  3035. var nrml = new CSG.Vector3D(0,0,0);
  3036. var maxSqr = 0;
  3037. for (var i = 0; i < this.numPoints; i++) {
  3038. var pt = this.pointBuffer[i];
  3039. var diff02 = CSG.Vector3D.Create(pt.pnt._x,pt.pnt._y,pt.pnt._z);
  3040. diff02 = diff02.minus(vtx[0].pnt);
  3041. var xprod = CSG.Vector3D.Create(u01._x,u01._y,u01._z);
  3042. xprod = xprod.cross(diff02);
  3043. var lenSqr = xprod.lengthSquared();
  3044. if (lenSqr > maxSqr &&
  3045. this.pointBuffer[i] != vtx[0] &&
  3046. this.pointBuffer[i] != vtx[1]) {
  3047. maxSqr = lenSqr;
  3048. vtx[2] = this.pointBuffer[i];
  3049. nrml.set(xprod._x,xprod._y,xprod._z);
  3050. // console.log("1",nrml);
  3051. }
  3052. }
  3053. if (Math.sqrt(maxSqr) <= 100*this.tolerance)
  3054. throw("Input points to hull appear to be co-linear");
  3055. nrml.normalize();
  3056. var maxDist = 0;
  3057. var d0 = vtx[2].pnt.dot(nrml);
  3058. for (var i = 0; i < this.numPoints; i++) {
  3059. var dist = Math.abs(this.pointBuffer[i].pnt.dot(nrml) - d0);
  3060. if (dist > maxDist &&
  3061. this.pointBuffer[i] != vtx[0] &&
  3062. this.pointBuffer[i] != vtx[1] &&
  3063. this.pointBuffer[i] != vtx[2]) {
  3064. maxDist = dist;
  3065. vtx[3] = this.pointBuffer[i];
  3066. }
  3067. }
  3068. if (Math.abs(maxDist) <= 100*this.tolerance)
  3069. throw("Input points appear to be coplanar");
  3070. // console.log("initial vertices:");
  3071. // console.log(vtx[0].index + ": " + vtx[0].pnt);
  3072. // console.log(vtx[1].index + ": " + vtx[1].pnt);
  3073. // console.log(vtx[2].index + ": " + vtx[2].pnt);
  3074. // console.log(vtx[3].index + ": " + vtx[3].pnt);
  3075. // we have our starting tetrahedron now. Let's assign the other points.
  3076. var tris = [new CSG.Face(), new CSG.Face(), new CSG.Face(), new CSG.Face()];
  3077. if (vtx[3].pnt.dot(nrml) - d0 < 0) {
  3078. tris[0] = tris[0].createTriangle (vtx[0], vtx[1], vtx[2]);
  3079. tris[1] = tris[1].createTriangle (vtx[3], vtx[1], vtx[0]);
  3080. tris[2] = tris[2].createTriangle (vtx[3], vtx[2], vtx[1]);
  3081. tris[3] = tris[3].createTriangle (vtx[3], vtx[0], vtx[2]);
  3082. for (var i = 0; i < 3; i++) {
  3083. var k = (i+1)%3;
  3084. tris[i+1].getEdge(1).setOpposite(tris[k+1].getEdge(0));
  3085. tris[i+1].getEdge(2).setOpposite(tris[0].getEdge(k));
  3086. }
  3087. }
  3088. else {
  3089. tris[0] = tris[0].createTriangle (vtx[0], vtx[2], vtx[1]);
  3090. tris[1] = tris[1].createTriangle (vtx[3], vtx[0], vtx[1]);
  3091. tris[2] = tris[2].createTriangle (vtx[3], vtx[1], vtx[2]);
  3092. tris[3] = tris[3].createTriangle (vtx[3], vtx[2], vtx[0]);
  3093. for (var i=0; i<3; i++) {
  3094. var k = (i+1)%3;
  3095. tris[i+1].getEdge(0).setOpposite (tris[k+1].getEdge(1));
  3096. tris[i+1].getEdge(2).setOpposite (tris[0].getEdge((3-i)%3));
  3097. }
  3098. }
  3099. for (var i=0; i < 4; i++)
  3100. this.faces.push(tris[i]);
  3101. // console.log(this.faces);
  3102. for (var i = 0; i < this.numPoints; i++) {
  3103. var v = this.pointBuffer[i];
  3104. if (v == vtx[0] || v == vtx[1] || v == vtx[2] || v == vtx[3])
  3105. continue;
  3106. maxDist = this.tolerance;
  3107. var maxFace = null;
  3108. for (var k=0; k < 4; k++) {
  3109. var dist = tris[k].distanceToPlane(v.pnt);
  3110. if (dist > maxDist) {
  3111. maxFace = tris[k];
  3112. maxDist = dist;
  3113. }
  3114. }
  3115. if (maxFace != null)
  3116. this.addPointToFace(v,maxFace);
  3117. }
  3118. }, // end of computeInitialSimplex()
  3119. addPointToFace: function(vtx,face) {
  3120. vtx.face = face;
  3121. // console.log("adding point: " + vtx.index + " to face: " + face.getVertexString());
  3122. if (face.outside == null)
  3123. this.claimed.add(vtx);
  3124. else
  3125. this.claimed.insertBefore(vtx,face.outside);
  3126. face.outside = vtx;
  3127. },
  3128. nextPointToAdd: function() {
  3129. // var i = this.claimed.head;
  3130. // while (i != null) {
  3131. // console.log("this.claimed: " + i.index);
  3132. // i = i.next;
  3133. // }
  3134. if (!this.claimed.isEmpty()) {
  3135. var eyeFace = this.claimed.first().face;
  3136. // console.log("eyeFace: ",eyeFace);
  3137. var eyeVtx = null;
  3138. var maxDist = 0;
  3139. for (var vtx=eyeFace.outside; vtx != null && vtx.face == eyeFace; vtx = vtx.next) {
  3140. var dist = eyeFace.distanceToPlane(vtx.pnt);
  3141. if (dist > maxDist) {
  3142. maxDist = dist;
  3143. eyeVtx = vtx;
  3144. }
  3145. }
  3146. return eyeVtx;
  3147. }
  3148. else return null;
  3149. },
  3150. addPointToHull: function(eyeVtx) {
  3151. this.horizon = [];
  3152. this.unclaimed.clear();
  3153. // console.log("Adding Point: " + eyeVtx.index +
  3154. // " which is " + eyeVtx.face.distanceToPlane(eyeVtx.pnt) +
  3155. // " above face ");
  3156. // console.log("in addPointToHull. About to call this.removePointFromFace");
  3157. this.removePointFromFace (eyeVtx, eyeVtx.face);
  3158. // console.log("just removed a point. Here is what is left in this.claimed:");
  3159. // for (var i = this.claimed.head; i != null; i = i.next) {
  3160. // console.log(i.index);
  3161. // }
  3162. this.calculateHorizon(eyeVtx.pnt, null, eyeVtx.face, this.horizon);
  3163. this.newFaces.clear();
  3164. this.addNewFaces(this.newFaces, eyeVtx, this.horizon);
  3165. // first merge pass ... merge faces which are non-convex
  3166. // as determined by the larger face
  3167. for (var face = this.newFaces.first(); face!=null; face=face.next) {
  3168. if (face.mark == 1) { // VISIBLE
  3169. while (this.doAdjacentMerge(face, 1)) {} // NONCONVEX_WRT_LARGER_FACE
  3170. }
  3171. }
  3172. // second merge pass ... merge faces which are non-convex
  3173. // wrt either face
  3174. for (var face = this.newFaces.first(); face!=null; face=face.next) {
  3175. if (face.mark == 2) { // NON_CONVEX
  3176. face.mark = 1; // VISIBLE
  3177. while (this.doAdjacentMerge(face, 2)) {} // NON_CONVEX
  3178. }
  3179. }
  3180. this.resolveUnclaimedPoints(this.newFaces);
  3181. },
  3182. removePointFromFace: function(vtx,face) {
  3183. // console.log("in removePointFromFace. About to delete: " + vtx.index);
  3184. if (vtx == face.outside) {
  3185. if (vtx.next != null && vtx.next.face == face)
  3186. face.outside = vtx.next;
  3187. else
  3188. face.outside = null;
  3189. }
  3190. this.claimed.delete(vtx);
  3191. },
  3192. calculateHorizon: function(eyePnt, edge0, face, horizon) {
  3193. // console.log("in calculateHorizon. Going to deleteFacePoints for " + face.getVertexString());
  3194. this.deleteFacePoints(face,null);
  3195. face.mark = 3; // DELETED
  3196. // console.log(" visiting face " + face.getVertexString());
  3197. // console.log("this.unclaimed now has: ");
  3198. // for (var i = this.unclaimed.head; i != null; i = i.next)
  3199. // console.log(i.index);
  3200. var edge;
  3201. if (edge0 == null) {
  3202. edge0 = face.getEdge(0);
  3203. edge = edge0;
  3204. }
  3205. else
  3206. edge = edge0.getNext();
  3207. do {
  3208. var oppFace = edge.oppositeFace();
  3209. if (oppFace.mark == 1) { // VISIBLE
  3210. if (oppFace.distanceToPlane(eyePnt) > this.tolerance)
  3211. this.calculateHorizon(eyePnt, edge.getOpposite(), oppFace, horizon);
  3212. else {
  3213. horizon.push(edge);
  3214. // console.log(" adding horizon edge " + edge.getVertexString());
  3215. }
  3216. }
  3217. edge = edge.getNext();
  3218. } while (edge != edge0);
  3219. },
  3220. oppFaceDistance: function(he) {
  3221. return he.face.distanceToPlane(he.opposite.face.getCentroid());
  3222. },
  3223. doAdjacentMerge: function(face,mergeType) {
  3224. var hedge = face.he0;
  3225. var convex = true;
  3226. do {
  3227. var oppFace = hedge.oppositeFace();
  3228. var merge = false;
  3229. var dist1;
  3230. var dist2;
  3231. if (mergeType == 2) { // NONCONVEX
  3232. // merge faces if they are definitively non-convex
  3233. if (this.oppFaceDistance(hedge) > -1 * this.tolerance ||
  3234. this.oppFaceDistance(hedge.opposite) > -1 * this.tolerance) {
  3235. merge = true;
  3236. }
  3237. }
  3238. else { // NONCONVEX_WRT_LARGER_FACE
  3239. // merge faces if they are parallel or non-convex
  3240. // wrt the larger face; otherwise, just mark the
  3241. // face non-convex for the second pass.
  3242. if (face.area > oppFace.area) {
  3243. if ((dist1 = this.oppFaceDistance(hedge)) > -this.tolerance)
  3244. merge = true;
  3245. else if (this.oppFaceDistance(hedge.opposite) > -this.tolerance)
  3246. convex = false;
  3247. }
  3248. else {
  3249. if (this.oppFaceDistance(hedge.opposite) > -this.tolerance)
  3250. merge = true;
  3251. else if (this.oppFaceDistance(hedge) > -this.tolerance)
  3252. convex = false;
  3253. }
  3254. }
  3255. if (merge) {
  3256. // console.log(" merging " + face.getVertexString() + " and " + oppFace.getVertexString());
  3257. var numd = face.mergeAdjacentFace(hedge, this.discardedFaces);
  3258. for (var i = 0; i < numd; i++) {
  3259. this.deleteFacePoints(this.discardedFaces[i], face);
  3260. }
  3261. // console.log(" result: " + face.getVertexString());
  3262. return true;
  3263. }
  3264. hedge = hedge.next;
  3265. } while (hedge != face.he0);
  3266. if (!convex)
  3267. face.mark = 2;
  3268. return false;
  3269. },
  3270. deleteFacePoints: function(face, absorbingFace) {
  3271. var faceVtxs = this.removeAllPointsFromFace(face);
  3272. if (faceVtxs != null) {
  3273. if (absorbingFace == null)
  3274. this.unclaimed.addAll(faceVtxs);
  3275. else {
  3276. var vtxNext = faceVtxs;
  3277. for (var vtx = vtxNext; vtx != null; vtx = vtxNext) {
  3278. vtxNext = vtx.next;
  3279. var dist = absorbingFace.distanceToPlane(vtx.pnt);
  3280. if (dist > this.tolerance) {
  3281. // console.log("in deleteFacePoints - going to add points to a face now");
  3282. this.addPointToFace(vtx, absorbingFace);
  3283. }
  3284. else
  3285. this.unclaimed.add(vtx);
  3286. }
  3287. }
  3288. }
  3289. },
  3290. removeAllPointsFromFace: function(face) {
  3291. if (face.outside != null) {
  3292. var end = face.outside;
  3293. while (end.next != null && end.next.face == face)
  3294. end = end.next;
  3295. // console.log("about to delete all points this.claimed in removeAllPointsFromFace: " + face.outside.index + " - " + end.index);
  3296. this.claimed.delete(face.outside, end);
  3297. end.next = null;
  3298. return face.outside;
  3299. }
  3300. else
  3301. return null;
  3302. },
  3303. addNewFaces: function(newFaces, eyeVtx, horizon) {
  3304. newFaces.clear();
  3305. var hedgeSidePrev = null;
  3306. var hedgeSideBegin = null;
  3307. for (var i = 0; i < horizon.length; i++) {
  3308. var horizonHe = horizon[i];
  3309. var hedgeSide = this.addAdjoiningFace(eyeVtx, horizonHe);
  3310. // console.log("new face: " + hedgeSide.face.getVertexString());
  3311. if (hedgeSidePrev != null)
  3312. hedgeSide.next.setOpposite(hedgeSidePrev);
  3313. else
  3314. hedgeSideBegin = hedgeSide;
  3315. newFaces.add(hedgeSide.getFace());
  3316. hedgeSidePrev = hedgeSide;
  3317. }
  3318. hedgeSideBegin.next.setOpposite(hedgeSidePrev);
  3319. },
  3320. addAdjoiningFace: function(eyeVtx, he) {
  3321. var face = new CSG.Face();
  3322. face = face.createTriangle (eyeVtx, he.tail(), he.head());
  3323. // console.log("in addAdjoiningFace. face is:",face);
  3324. this.faces.push (face);
  3325. face.getEdge(-1).setOpposite(he.getOpposite());
  3326. return face.getEdge(0);
  3327. },
  3328. resolveUnclaimedPoints: function(newFaces) {
  3329. // console.log("in resolveUnclaimedPoints, which has:");
  3330. var vtxNext = this.unclaimed.first();
  3331. for (var vtx = vtxNext; vtx != null; vtx = vtxNext) {
  3332. // console.log(vtx.index);
  3333. vtxNext = vtx.next;
  3334. var maxDist = this.tolerance;
  3335. var maxFace = null;
  3336. for (var newFace = newFaces.first(); newFace != null; newFace = newFace.next) {
  3337. if (newFace.mark == 1) { // VISIBLE
  3338. var dist = newFace.distanceToPlane(vtx.pnt);
  3339. if (dist > maxDist) {
  3340. maxDist = dist;
  3341. maxFace = newFace;
  3342. }
  3343. if (maxDist > 1000*this.tolerance)
  3344. break;
  3345. }
  3346. }
  3347. if (maxFace != null) {
  3348. this.addPointToFace(vtx, maxFace);
  3349. // if (vtx.index == this.findIndex) ;
  3350. // console.log(this.findIndex + " CLAIMED BY " + maxFace.getVertexString());
  3351. }
  3352. // else if (vtx.index == this.findIndex);
  3353. // console.log(this.findIndex + " DISCARDED");
  3354. }
  3355. },
  3356. reindexFacesAndVertices: function() {
  3357. for (var i = 0; i < this.numPoints; i++)
  3358. this.pointBuffer[i].index = -1;
  3359. // remove inactive faces and mark active vertices
  3360. this.numFaces = 0;
  3361. var nFaces = [];
  3362. for (var i = 0; i < this.faces.length; i++) {
  3363. var face = this.faces[i];
  3364. if (face.mark == 1) {
  3365. this.markFaceVertices(face,0);
  3366. this.numFaces++;
  3367. nFaces.push(face);
  3368. }
  3369. }
  3370. this.faces = nFaces;
  3371. // reindex vertices
  3372. this.numVertices = 0;
  3373. for (var i = 0; i < this.numPoints; i++) {
  3374. var vtx = this.pointBuffer[i];
  3375. if (vtx.index == 0) {
  3376. this.vertexPointIndices[this.numVertices] = i;
  3377. vtx.index = this.numVertices++;
  3378. }
  3379. }
  3380. },
  3381. markFaceVertices: function(face, mark) {
  3382. var he0 = face.getFirstEdge();
  3383. var he = he0;
  3384. do {
  3385. he.head().index = mark;
  3386. he = he.next
  3387. } while (he != he0);
  3388. },
  3389. // getFaces: get the faces of the completed hull.
  3390. getFaces: function() {
  3391. var allFaces = [];
  3392. for (var i = 0; i < this.faces.length; i++) {
  3393. var face = this.faces[i];
  3394. allFaces.push([]);
  3395. this.getFaceIndices(allFaces[i], face);
  3396. }
  3397. return allFaces;
  3398. },
  3399. getFaceIndices: function(indices, face) {
  3400. var ccw = true;
  3401. var indexedFromOne = false;
  3402. var pointRelative = true;
  3403. var hedge = face.he0;
  3404. var k = 0;
  3405. do {
  3406. var idx = hedge.head().index;
  3407. if (pointRelative)
  3408. idx = this.vertexPointIndices[idx];
  3409. if (indexedFromOne)
  3410. idx++;
  3411. indices[k++] = idx;
  3412. hedge = (ccw ? hedge.next : hedge.prev);
  3413. } while (hedge != face.he0);
  3414. },
  3415. getVertices: function() {
  3416. var coords = [];
  3417. for (var i = 0; i < this.numVertices; i++) {
  3418. var pnt = this.pointBuffer[this.vertexPointIndices[i]].pnt;
  3419. coords.push([pnt._x,pnt._y,pnt._z]);
  3420. }
  3421. return coords;
  3422. },
  3423. printPoints: function() {
  3424. for (var i = 0; i < this.pointBuffer.length; i++) {
  3425. var pnt = this.pointBuffer[i].pnt;
  3426. console.log(i + ": " + pnt._x + ", " + pnt._y + ", " + pnt._z);
  3427. }
  3428. }
  3429. } // end of quickHull3D.prototype
  3430. // # class Plane
  3431. // Represents a plane in 3D space.
  3432. CSG.Plane = function(normal, w) {
  3433. this.normal = normal;
  3434. this.w = w;
  3435. };
  3436. // create from an untyped object with identical property names:
  3437. CSG.Plane.fromObject = function(obj) {
  3438. var normal = new CSG.Vector3D(obj.normal);
  3439. var w = parseFloat(obj.w);
  3440. return new CSG.Plane(normal, w);
  3441. };
  3442. // `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a
  3443. // point is on the plane.
  3444. CSG.Plane.EPSILON = 1e-5;
  3445. CSG.Plane.fromVector3Ds = function(a, b, c) {
  3446. var n = b.minus(a).cross(c.minus(a)).unit();
  3447. return new CSG.Plane(n, n.dot(a));
  3448. };
  3449. // like fromVector3Ds, but allow the vectors to be on one point or one line
  3450. // in such a case a random plane through the given points is constructed
  3451. CSG.Plane.anyPlaneFromVector3Ds = function(a, b, c) {
  3452. var v1 = b.minus(a);
  3453. var v2 = c.minus(a);
  3454. if (v1.length() < 1e-5) {
  3455. v1 = v2.randomNonParallelVector();
  3456. }
  3457. if (v2.length() < 1e-5) {
  3458. v2 = v1.randomNonParallelVector();
  3459. }
  3460. var normal = v1.cross(v2);
  3461. if (normal.length() < 1e-5) {
  3462. // this would mean that v1 == v2.negated()
  3463. v2 = v1.randomNonParallelVector();
  3464. normal = v1.cross(v2);
  3465. }
  3466. normal = normal.unit();
  3467. return new CSG.Plane(normal, normal.dot(a));
  3468. };
  3469. CSG.Plane.fromPoints = function(a, b, c) {
  3470. a = new CSG.Vector3D(a);
  3471. b = new CSG.Vector3D(b);
  3472. c = new CSG.Vector3D(c);
  3473. return CSG.Plane.fromVector3Ds(a, b, c);
  3474. };
  3475. CSG.Plane.fromNormalAndPoint = function(normal, point) {
  3476. normal = new CSG.Vector3D(normal);
  3477. point = new CSG.Vector3D(point);
  3478. normal = normal.unit();
  3479. var w = point.dot(normal);
  3480. return new CSG.Plane(normal, w);
  3481. };
  3482. CSG.Plane.prototype = {
  3483. flipped: function() {
  3484. return new CSG.Plane(this.normal.negated(), -this.w);
  3485. },
  3486. getTag: function() {
  3487. var result = this.tag;
  3488. if (!result) {
  3489. result = CSG.getTag();
  3490. this.tag = result;
  3491. }
  3492. return result;
  3493. },
  3494. equals: function(n) {
  3495. return this.normal.equals(n.normal) && this.w == n.w;
  3496. },
  3497. transform: function(matrix4x4) {
  3498. var ismirror = matrix4x4.isMirroring();
  3499. // get two vectors in the plane:
  3500. var r = this.normal.randomNonParallelVector();
  3501. var u = this.normal.cross(r);
  3502. var v = this.normal.cross(u);
  3503. // get 3 points in the plane:
  3504. var point1 = this.normal.times(this.w);
  3505. var point2 = point1.plus(u);
  3506. var point3 = point1.plus(v);
  3507. // transform the points:
  3508. point1 = point1.multiply4x4(matrix4x4);
  3509. point2 = point2.multiply4x4(matrix4x4);
  3510. point3 = point3.multiply4x4(matrix4x4);
  3511. // and create a new plane from the transformed points:
  3512. var newplane = CSG.Plane.fromVector3Ds(point1, point2, point3);
  3513. if (ismirror) {
  3514. // the transform is mirroring
  3515. // We should mirror the plane:
  3516. newplane = newplane.flipped();
  3517. }
  3518. return newplane;
  3519. },
  3520. // Returns object:
  3521. // .type:
  3522. // 0: coplanar-front
  3523. // 1: coplanar-back
  3524. // 2: front
  3525. // 3: back
  3526. // 4: spanning
  3527. // In case the polygon is spanning, returns:
  3528. // .front: a CSG.Polygon of the front part
  3529. // .back: a CSG.Polygon of the back part
  3530. splitPolygon: function(polygon) {
  3531. var result = {
  3532. type: null,
  3533. front: null,
  3534. back: null
  3535. };
  3536. // cache in local vars (speedup):
  3537. var planenormal = this.normal;
  3538. var vertices = polygon.vertices;
  3539. var numvertices = vertices.length;
  3540. if (polygon.plane.equals(this)) {
  3541. result.type = 0;
  3542. } else {
  3543. var EPS = CSG.Plane.EPSILON;
  3544. var thisw = this.w;
  3545. var hasfront = false;
  3546. var hasback = false;
  3547. var vertexIsBack = [];
  3548. var MINEPS = -EPS;
  3549. for (var i = 0; i < numvertices; i++) {
  3550. var t = planenormal.dot(vertices[i].pos) - thisw;
  3551. var isback = (t < 0);
  3552. vertexIsBack.push(isback);
  3553. if (t > EPS) hasfront = true;
  3554. if (t < MINEPS) hasback = true;
  3555. }
  3556. if ((!hasfront) && (!hasback)) {
  3557. // all points coplanar
  3558. var t = planenormal.dot(polygon.plane.normal);
  3559. result.type = (t >= 0) ? 0 : 1;
  3560. } else if (!hasback) {
  3561. result.type = 2;
  3562. } else if (!hasfront) {
  3563. result.type = 3;
  3564. } else {
  3565. // spanning
  3566. result.type = 4;
  3567. var frontvertices = [],
  3568. backvertices = [];
  3569. var isback = vertexIsBack[0];
  3570. for (var vertexindex = 0; vertexindex < numvertices; vertexindex++) {
  3571. var vertex = vertices[vertexindex];
  3572. var nextvertexindex = vertexindex + 1;
  3573. if (nextvertexindex >= numvertices) nextvertexindex = 0;
  3574. var nextisback = vertexIsBack[nextvertexindex];
  3575. if (isback == nextisback) {
  3576. // line segment is on one side of the plane:
  3577. if (isback) {
  3578. backvertices.push(vertex);
  3579. } else {
  3580. frontvertices.push(vertex);
  3581. }
  3582. } else {
  3583. // line segment intersects plane:
  3584. var point = vertex.pos;
  3585. var nextpoint = vertices[nextvertexindex].pos;
  3586. var intersectionpoint = this.splitLineBetweenPoints(point, nextpoint);
  3587. var intersectionvertex = new CSG.Vertex(intersectionpoint);
  3588. if (isback) {
  3589. backvertices.push(vertex);
  3590. backvertices.push(intersectionvertex);
  3591. frontvertices.push(intersectionvertex);
  3592. } else {
  3593. frontvertices.push(vertex);
  3594. frontvertices.push(intersectionvertex);
  3595. backvertices.push(intersectionvertex);
  3596. }
  3597. }
  3598. isback = nextisback;
  3599. } // for vertexindex
  3600. // remove duplicate vertices:
  3601. var EPS_SQUARED = CSG.Plane.EPSILON * CSG.Plane.EPSILON;
  3602. if (backvertices.length >= 3) {
  3603. var prevvertex = backvertices[backvertices.length - 1];
  3604. for (var vertexindex = 0; vertexindex < backvertices.length; vertexindex++) {
  3605. var vertex = backvertices[vertexindex];
  3606. if (vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) {
  3607. backvertices.splice(vertexindex, 1);
  3608. vertexindex--;
  3609. }
  3610. prevvertex = vertex;
  3611. }
  3612. }
  3613. if (frontvertices.length >= 3) {
  3614. var prevvertex = frontvertices[frontvertices.length - 1];
  3615. for (var vertexindex = 0; vertexindex < frontvertices.length; vertexindex++) {
  3616. var vertex = frontvertices[vertexindex];
  3617. if (vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) {
  3618. frontvertices.splice(vertexindex, 1);
  3619. vertexindex--;
  3620. }
  3621. prevvertex = vertex;
  3622. }
  3623. }
  3624. if (frontvertices.length >= 3) {
  3625. result.front = new CSG.Polygon(frontvertices, polygon.shared, polygon.plane);
  3626. }
  3627. if (backvertices.length >= 3) {
  3628. result.back = new CSG.Polygon(backvertices, polygon.shared, polygon.plane);
  3629. }
  3630. }
  3631. }
  3632. return result;
  3633. },
  3634. // robust splitting of a line by a plane
  3635. // will work even if the line is parallel to the plane
  3636. splitLineBetweenPoints: function(p1, p2) {
  3637. var direction = p2.minus(p1);
  3638. var labda = (this.w - this.normal.dot(p1)) / this.normal.dot(direction);
  3639. if (isNaN(labda)) labda = 0;
  3640. if (labda > 1) labda = 1;
  3641. if (labda < 0) labda = 0;
  3642. var result = p1.plus(direction.times(labda));
  3643. return result;
  3644. },
  3645. // returns CSG.Vector3D
  3646. intersectWithLine: function(line3d) {
  3647. return line3d.intersectWithPlane(this);
  3648. },
  3649. // intersection of two planes
  3650. intersectWithPlane: function(plane) {
  3651. return CSG.Line3D.fromPlanes(this, plane);
  3652. },
  3653. signedDistanceToPoint: function(point) {
  3654. var t = this.normal.dot(point) - this.w;
  3655. return t;
  3656. },
  3657. toString: function() {
  3658. return "[normal: " + this.normal.toString() + ", w: " + this.w + "]";
  3659. },
  3660. mirrorPoint: function(point3d) {
  3661. var distance = this.signedDistanceToPoint(point3d);
  3662. var mirrored = point3d.minus(this.normal.times(distance * 2.0));
  3663. return mirrored;
  3664. }
  3665. };
  3666. // # class Polygon
  3667. // Represents a convex polygon. The vertices used to initialize a polygon must
  3668. // be coplanar and form a convex loop. They do not have to be `CSG.Vertex`
  3669. // instances but they must behave similarly (duck typing can be used for
  3670. // customization).
  3671. //
  3672. // Each convex polygon has a `shared` property, which is shared between all
  3673. // polygons that are clones of each other or were split from the same polygon.
  3674. // This can be used to define per-polygon properties (such as surface color).
  3675. //
  3676. // The plane of the polygon is calculated from the vertex coordinates
  3677. // To avoid unnecessary recalculation, the plane can alternatively be
  3678. // passed as the third argument
  3679. CSG.Polygon = function(vertices, shared, plane) {
  3680. this.vertices = vertices;
  3681. if (!shared) shared = CSG.Polygon.defaultShared;
  3682. this.shared = shared;
  3683. //var numvertices = vertices.length;
  3684. if (arguments.length >= 3) {
  3685. this.plane = plane;
  3686. } else {
  3687. this.plane = CSG.Plane.fromVector3Ds(vertices[0].pos, vertices[1].pos, vertices[2].pos);
  3688. }
  3689. if (_CSGDEBUG) {
  3690. this.checkIfConvex();
  3691. }
  3692. };
  3693. // create from an untyped object with identical property names:
  3694. CSG.Polygon.fromObject = function(obj) {
  3695. var vertices = obj.vertices.map(function(v) {
  3696. return CSG.Vertex.fromObject(v);
  3697. });
  3698. var shared = CSG.Polygon.Shared.fromObject(obj.shared);
  3699. var plane = CSG.Plane.fromObject(obj.plane);
  3700. return new CSG.Polygon(vertices, shared, plane);
  3701. };
  3702. CSG.Polygon.prototype = {
  3703. // check whether the polygon is convex (it should be, otherwise we will get unexpected results)
  3704. checkIfConvex: function() {
  3705. if (!CSG.Polygon.verticesConvex(this.vertices, this.plane.normal)) {
  3706. CSG.Polygon.verticesConvex(this.vertices, this.plane.normal);
  3707. throw new Error("Not convex!");
  3708. }
  3709. },
  3710. sC: function(args) {
  3711. var newshared = CSG.Polygon.Shared.fromColor.apply(this, arguments);
  3712. this.shared = newshared;
  3713. return this;
  3714. },
  3715. getSignedVolume: function() {
  3716. var signedVolume = 0;
  3717. for (var i = 0; i < this.vertices.length - 2; i++) {
  3718. signedVolume += this.vertices[0].pos.dot(this.vertices[i+1].pos
  3719. .cross(this.vertices[i+2].pos));
  3720. }
  3721. signedVolume /= 6;
  3722. return signedVolume;
  3723. },
  3724. // Note: could calculate vectors only once to speed up
  3725. getArea: function() {
  3726. var polygonArea = 0;
  3727. for (var i = 0; i < this.vertices.length - 2; i++) {
  3728. polygonArea += this.vertices[i+1].pos.minus(this.vertices[0].pos)
  3729. .cross(this.vertices[i+2].pos.minus(this.vertices[i+1].pos)).length();
  3730. }
  3731. polygonArea /= 2;
  3732. return polygonArea;
  3733. },
  3734. // accepts array of features to calculate
  3735. // returns array of results
  3736. getTetraFeatures: function(features) {
  3737. var result = [];
  3738. features.forEach(function(feature) {
  3739. if (feature == 'volume') {
  3740. result.push(this.getSignedVolume());
  3741. } else if (feature == 'area') {
  3742. result.push(this.getArea());
  3743. }
  3744. }, this);
  3745. return result;
  3746. },
  3747. // Extrude a polygon into the direction offsetvector
  3748. // Returns a CSG object
  3749. extrude: function(offsetvector) {
  3750. var newpolygons = [];
  3751. var polygon1 = this;
  3752. var direction = polygon1.plane.normal.dot(offsetvector);
  3753. if (direction > 0) {
  3754. polygon1 = polygon1.flipped();
  3755. }
  3756. newpolygons.push(polygon1);
  3757. var polygon2 = polygon1.tr(offsetvector);
  3758. var numvertices = this.vertices.length;
  3759. for (var i = 0; i < numvertices; i++) {
  3760. var sidefacepoints = [];
  3761. var nexti = (i < (numvertices - 1)) ? i + 1 : 0;
  3762. sidefacepoints.push(polygon1.vertices[i].pos);
  3763. sidefacepoints.push(polygon2.vertices[i].pos);
  3764. sidefacepoints.push(polygon2.vertices[nexti].pos);
  3765. sidefacepoints.push(polygon1.vertices[nexti].pos);
  3766. var sidefacepolygon = CSG.Polygon.createFromPoints(sidefacepoints, this.shared);
  3767. newpolygons.push(sidefacepolygon);
  3768. }
  3769. polygon2 = polygon2.flipped();
  3770. newpolygons.push(polygon2);
  3771. return CSG.fromPolygons(newpolygons);
  3772. },
  3773. tr: function(offset) {
  3774. return this.transform(CSG.Matrix4x4.translation(offset));
  3775. },
  3776. // returns an array with a CSG.Vector3D (center point) and a radius
  3777. boundingSphere: function() {
  3778. if (!this.cachedBoundingSphere) {
  3779. var box = this.boundingBox();
  3780. var middle = box[0].plus(box[1]).times(0.5);
  3781. var radius3 = box[1].minus(middle);
  3782. var radius = radius3.length();
  3783. this.cachedBoundingSphere = [middle, radius];
  3784. }
  3785. return this.cachedBoundingSphere;
  3786. },
  3787. // returns an array of two CSG.Vector3Ds (minimum coordinates and maximum coordinates)
  3788. boundingBox: function() {
  3789. if (!this.cachedBoundingBox) {
  3790. var minpoint, maxpoint;
  3791. var vertices = this.vertices;
  3792. var numvertices = vertices.length;
  3793. if (numvertices === 0) {
  3794. minpoint = new CSG.Vector3D(0, 0, 0);
  3795. } else {
  3796. minpoint = vertices[0].pos;
  3797. }
  3798. maxpoint = minpoint;
  3799. for (var i = 1; i < numvertices; i++) {
  3800. var point = vertices[i].pos;
  3801. minpoint = minpoint.min(point);
  3802. maxpoint = maxpoint.max(point);
  3803. }
  3804. this.cachedBoundingBox = [minpoint, maxpoint];
  3805. }
  3806. return this.cachedBoundingBox;
  3807. },
  3808. flipped: function() {
  3809. var newvertices = this.vertices.map(function(v) {
  3810. return v.flipped();
  3811. });
  3812. newvertices.reverse();
  3813. var newplane = this.plane.flipped();
  3814. return new CSG.Polygon(newvertices, this.shared, newplane);
  3815. },
  3816. // Affine transformation of polygon. Returns a new CSG.Polygon
  3817. transform: function(matrix4x4) {
  3818. var newvertices = this.vertices.map(function(v) {
  3819. return v.transform(matrix4x4);
  3820. });
  3821. var newplane = this.plane.transform(matrix4x4);
  3822. if (matrix4x4.isMirroring()) {
  3823. // need to reverse the vertex order
  3824. // in order to preserve the inside/outside orientation:
  3825. newvertices.reverse();
  3826. }
  3827. return new CSG.Polygon(newvertices, this.shared, newplane);
  3828. },
  3829. toString: function() {
  3830. var result = "Polygon plane: " + this.plane.toString() + "\n";
  3831. this.vertices.map(function(vertex) {
  3832. result += " " + vertex.toString() + "\n";
  3833. });
  3834. return result;
  3835. },
  3836. // project the 3D polygon onto a plane
  3837. projectToOrthoNormalBasis: function(orthobasis) {
  3838. var points2d = this.vertices.map(function(vertex) {
  3839. return orthobasis.to2D(vertex.pos);
  3840. });
  3841. var result = CAG.fromPointsNoCheck(points2d);
  3842. var area = result.area();
  3843. if (Math.abs(area) < 1e-5) {
  3844. // the polygon was perpendicular to the orthnormal plane. The resulting 2D polygon would be degenerate
  3845. // return an empty area instead:
  3846. result = new CAG();
  3847. } else if (area < 0) {
  3848. result = result.flipped();
  3849. }
  3850. return result;
  3851. },
  3852. /**
  3853. * Creates solid from slices (CSG.Polygon) by generating walls
  3854. * @param {Object} options Solid generating options
  3855. * - numslices {Number} Number of slices to be generated
  3856. * - callback(t, slice) {Function} Callback function generating slices.
  3857. * arguments: t = [0..1], slice = [0..numslices - 1]
  3858. * return: CSG.Polygon or null to skip
  3859. * - loop {Boolean} no flats, only walls, it's used to generate solids like a tor
  3860. */
  3861. solidFromSlices: function(options) {
  3862. var polygons = [],
  3863. csg = null,
  3864. prev = null,
  3865. bottom = null,
  3866. top = null,
  3867. numSlices = 2,
  3868. bLoop = false,
  3869. fnCallback,
  3870. flipped = null;
  3871. if (options) {
  3872. bLoop = Boolean(options['loop']);
  3873. if (options.numslices)
  3874. numSlices = options.numslices;
  3875. if (options.callback)
  3876. fnCallback = options.callback;
  3877. }
  3878. if (!fnCallback) {
  3879. var square = new CSG.Polygon.createFromPoints([
  3880. [0, 0, 0],
  3881. [1, 0, 0],
  3882. [1, 1, 0],
  3883. [0, 1, 0]
  3884. ]);
  3885. fnCallback = function(t, slice) {
  3886. return t == 0 || t == 1 ? square.tr([0, 0, t]) : null;
  3887. }
  3888. }
  3889. for (var i = 0, iMax = numSlices - 1; i <= iMax; i++) {
  3890. csg = fnCallback.call(this, i / iMax, i);
  3891. if (csg) {
  3892. if (!(csg instanceof CSG.Polygon)) {
  3893. throw new Error("CSG.Polygon.solidFromSlices callback error: CSG.Polygon expected");
  3894. }
  3895. csg.checkIfConvex();
  3896. if (prev) { //generate walls
  3897. if (flipped === null) { //not generated yet
  3898. flipped = prev.plane.signedDistanceToPoint(csg.vertices[0].pos) < 0;
  3899. }
  3900. this._addWalls(polygons, prev, csg, flipped);
  3901. } else { //the first - will be a bottom
  3902. bottom = csg;
  3903. }
  3904. prev = csg;
  3905. } //callback can return null to skip that slice
  3906. }
  3907. top = csg;
  3908. if (bLoop) {
  3909. var bSameTopBottom = bottom.vertices.length == top.vertices.length &&
  3910. bottom.vertices.every(function(v, index) {
  3911. return v.pos.equals(top.vertices[index].pos)
  3912. });
  3913. //if top and bottom are not the same -
  3914. //generate walls between them
  3915. if (!bSameTopBottom) {
  3916. this._addWalls(polygons, top, bottom, flipped);
  3917. } //else - already generated
  3918. } else {
  3919. //save top and bottom
  3920. //TODO: flip if necessary
  3921. polygons.unshift(flipped ? bottom : bottom.flipped());
  3922. polygons.push(flipped ? top.flipped() : top);
  3923. }
  3924. return CSG.fromPolygons(polygons);
  3925. },
  3926. /**
  3927. *
  3928. * @param walls Array of wall polygons
  3929. * @param bottom Bottom polygon
  3930. * @param top Top polygon
  3931. */
  3932. _addWalls: function(walls, bottom, top, bFlipped) {
  3933. var bottomPoints = bottom.vertices.slice(0), //make a copy
  3934. topPoints = top.vertices.slice(0), //make a copy
  3935. color = top.shared || null;
  3936. //check if bottom perimeter is closed
  3937. if (!bottomPoints[0].pos.equals(bottomPoints[bottomPoints.length - 1].pos)) {
  3938. bottomPoints.push(bottomPoints[0]);
  3939. }
  3940. //check if top perimeter is closed
  3941. if (!topPoints[0].pos.equals(topPoints[topPoints.length - 1].pos)) {
  3942. topPoints.push(topPoints[0]);
  3943. }
  3944. if (bFlipped) {
  3945. bottomPoints = bottomPoints.reverse();
  3946. topPoints = topPoints.reverse();
  3947. }
  3948. var iTopLen = topPoints.length - 1,
  3949. iBotLen = bottomPoints.length - 1,
  3950. iExtra = iTopLen - iBotLen, //how many extra triangles we need
  3951. bMoreTops = iExtra > 0,
  3952. bMoreBottoms = iExtra < 0;
  3953. var aMin = []; //indexes to start extra triangles (polygon with minimal square)
  3954. //init - we need exactly /iExtra/ small triangles
  3955. for (var i = Math.abs(iExtra); i > 0; i--) {
  3956. aMin.push({
  3957. len: Infinity,
  3958. index: -1
  3959. });
  3960. }
  3961. var len;
  3962. if (bMoreBottoms) {
  3963. for (var i = 0; i < iBotLen; i++) {
  3964. len = bottomPoints[i].pos.distanceToSquared(bottomPoints[i + 1].pos);
  3965. //find the element to replace
  3966. for (var j = aMin.length - 1; j >= 0; j--) {
  3967. if (aMin[j].len > len) {
  3968. aMin[j].len = len;
  3969. aMin.index = j;
  3970. break;
  3971. }
  3972. } //for
  3973. }
  3974. } else if (bMoreTops) {
  3975. for (var i = 0; i < iTopLen; i++) {
  3976. len = topPoints[i].pos.distanceToSquared(topPoints[i + 1].pos);
  3977. //find the element to replace
  3978. for (var j = aMin.length - 1; j >= 0; j--) {
  3979. if (aMin[j].len > len) {
  3980. aMin[j].len = len;
  3981. aMin.index = j;
  3982. break;
  3983. }
  3984. } //for
  3985. }
  3986. } //if
  3987. //sort by index
  3988. aMin.sort(fnSortByIndex);
  3989. var getTriangle = function addWallsPutTriangle(pointA, pointB, pointC, color) {
  3990. return new CSG.Polygon([pointA, pointB, pointC], color);
  3991. //return bFlipped ? triangle.flipped() : triangle;
  3992. };
  3993. var bpoint = bottomPoints[0],
  3994. tpoint = topPoints[0],
  3995. secondPoint,
  3996. nBotFacet, nTopFacet; //length of triangle facet side
  3997. for (var iB = 0, iT = 0, iMax = iTopLen + iBotLen; iB + iT < iMax;) {
  3998. if (aMin.length) {
  3999. if (bMoreTops && iT == aMin[0].index) { //one vertex is on the bottom, 2 - on the top
  4000. secondPoint = topPoints[++iT];
  4001. //console.log('<<< extra top: ' + secondPoint + ', ' + tpoint + ', bottom: ' + bpoint);
  4002. walls.push(getTriangle(
  4003. secondPoint, tpoint, bpoint, color
  4004. ));
  4005. tpoint = secondPoint;
  4006. aMin.shift();
  4007. continue;
  4008. } else if (bMoreBottoms && iB == aMin[0].index) {
  4009. secondPoint = bottomPoints[++iB];
  4010. walls.push(getTriangle(
  4011. tpoint, bpoint, secondPoint, color
  4012. ));
  4013. bpoint = secondPoint;
  4014. aMin.shift();
  4015. continue;
  4016. }
  4017. }
  4018. //choose the shortest path
  4019. if (iB < iBotLen) { //one vertex is on the top, 2 - on the bottom
  4020. nBotFacet = tpoint.pos.distanceToSquared(bottomPoints[iB + 1].pos);
  4021. } else {
  4022. nBotFacet = Infinity;
  4023. }
  4024. if (iT < iTopLen) { //one vertex is on the bottom, 2 - on the top
  4025. nTopFacet = bpoint.pos.distanceToSquared(topPoints[iT + 1].pos);
  4026. } else {
  4027. nTopFacet = Infinity;
  4028. }
  4029. if (nBotFacet <= nTopFacet) {
  4030. secondPoint = bottomPoints[++iB];
  4031. walls.push(getTriangle(
  4032. tpoint, bpoint, secondPoint, color
  4033. ));
  4034. bpoint = secondPoint;
  4035. } else if (iT < iTopLen) { //nTopFacet < Infinity
  4036. secondPoint = topPoints[++iT];
  4037. //console.log('<<< top: ' + secondPoint + ', ' + tpoint + ', bottom: ' + bpoint);
  4038. walls.push(getTriangle(
  4039. secondPoint, tpoint, bpoint, color
  4040. ));
  4041. tpoint = secondPoint;
  4042. };
  4043. }
  4044. return walls;
  4045. }
  4046. };
  4047. CSG.Polygon.verticesConvex = function(vertices, planenormal) {
  4048. var numvertices = vertices.length;
  4049. if (numvertices > 2) {
  4050. var prevprevpos = vertices[numvertices - 2].pos;
  4051. var prevpos = vertices[numvertices - 1].pos;
  4052. for (var i = 0; i < numvertices; i++) {
  4053. var pos = vertices[i].pos;
  4054. if (!CSG.Polygon.isConvexPoint(prevprevpos, prevpos, pos, planenormal)) {
  4055. return false;
  4056. }
  4057. prevprevpos = prevpos;
  4058. prevpos = pos;
  4059. }
  4060. }
  4061. return true;
  4062. };
  4063. // Create a polygon from the given points
  4064. CSG.Polygon.createFromPoints = function(points, shared, plane) {
  4065. var normal;
  4066. if (arguments.length < 3) {
  4067. // initially set a dummy vertex normal:
  4068. normal = new CSG.Vector3D(0, 0, 0);
  4069. } else {
  4070. normal = plane.normal;
  4071. }
  4072. var vertices = [];
  4073. points.map(function(p) {
  4074. var vec = new CSG.Vector3D(p);
  4075. var vertex = new CSG.Vertex(vec);
  4076. vertices.push(vertex);
  4077. });
  4078. var polygon;
  4079. if (arguments.length < 3) {
  4080. polygon = new CSG.Polygon(vertices, shared);
  4081. } else {
  4082. polygon = new CSG.Polygon(vertices, shared, plane);
  4083. }
  4084. return polygon;
  4085. };
  4086. // calculate whether three points form a convex corner
  4087. // prevpoint, point, nextpoint: the 3 coordinates (CSG.Vector3D instances)
  4088. // normal: the normal vector of the plane
  4089. CSG.Polygon.isConvexPoint = function(prevpoint, point, nextpoint, normal) {
  4090. var crossproduct = point.minus(prevpoint).cross(nextpoint.minus(point));
  4091. var crossdotnormal = crossproduct.dot(normal);
  4092. return (crossdotnormal >= 0);
  4093. };
  4094. CSG.Polygon.isStrictlyConvexPoint = function(prevpoint, point, nextpoint, normal) {
  4095. var crossproduct = point.minus(prevpoint).cross(nextpoint.minus(point));
  4096. var crossdotnormal = crossproduct.dot(normal);
  4097. return (crossdotnormal >= 1e-5);
  4098. };
  4099. // # class CSG.Polygon.Shared
  4100. // Holds the shared properties for each polygon (currently only color)
  4101. // Constructor expects a 4 element array [r,g,b,a], values from 0 to 1, or null
  4102. CSG.Polygon.Shared = function(color) {
  4103. if(color !== null)
  4104. {
  4105. if (color.length != 4) {
  4106. throw new Error("Expecting 4 element array");
  4107. }
  4108. }
  4109. this.color = color;
  4110. };
  4111. CSG.Polygon.Shared.fromObject = function(obj) {
  4112. return new CSG.Polygon.Shared(obj.color);
  4113. };
  4114. // Create CSG.Polygon.Shared from a color, can be called as follows:
  4115. // var s = CSG.Polygon.Shared.fromColor(r,g,b [,a])
  4116. // var s = CSG.Polygon.Shared.fromColor([r,g,b [,a]])
  4117. CSG.Polygon.Shared.fromColor = function(args) {
  4118. var color;
  4119. if(arguments.length == 1) {
  4120. color = arguments[0].slice(); // make deep copy
  4121. }
  4122. else {
  4123. color = [];
  4124. for(var i=0; i < arguments.length; i++) {
  4125. color.push(arguments[i]);
  4126. }
  4127. }
  4128. if(color.length == 3) {
  4129. color.push(1);
  4130. } else if(color.length != 4) {
  4131. throw new Error("setColor expects either an array with 3 or 4 elements, or 3 or 4 parameters.");
  4132. }
  4133. return new CSG.Polygon.Shared(color);
  4134. };
  4135. CSG.Polygon.Shared.prototype = {
  4136. getTag: function() {
  4137. var result = this.tag;
  4138. if (!result) {
  4139. result = CSG.getTag();
  4140. this.tag = result;
  4141. }
  4142. return result;
  4143. },
  4144. // get a string uniquely identifying this object
  4145. getHash: function() {
  4146. if (!this.color) return "null";
  4147. return this.color.join("/");
  4148. }
  4149. };
  4150. CSG.Polygon.defaultShared = new CSG.Polygon.Shared(null);
  4151. // # class PolygonTreeNode
  4152. // This class manages hierarchical splits of polygons
  4153. // At the top is a root node which doesn hold a polygon, only child PolygonTreeNodes
  4154. // Below that are zero or more 'top' nodes; each holds a polygon. The polygons can be in different planes
  4155. // splitByPlane() splits a node by a plane. If the plane intersects the polygon, two new child nodes
  4156. // are created holding the splitted polygon.
  4157. // getPolygons() retrieves the polygon from the tree. If for PolygonTreeNode the polygon is split but
  4158. // the two split parts (child nodes) are still intact, then the unsplit polygon is returned.
  4159. // This ensures that we can safely split a polygon into many fragments. If the fragments are untouched,
  4160. // getPolygons() will return the original unsplit polygon instead of the fragments.
  4161. // remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated
  4162. // since they are no longer intact.
  4163. // constructor creates the root node:
  4164. CSG.PolygonTreeNode = function() {
  4165. this.parent = null;
  4166. this.children = [];
  4167. this.polygon = null;
  4168. this.removed = false;
  4169. };
  4170. CSG.PolygonTreeNode.prototype = {
  4171. // fill the tree with polygons. Should be called on the root node only; child nodes must
  4172. // always be a derivate (split) of the parent node.
  4173. addPolygons: function(polygons) {
  4174. if (!this.isRootNode())
  4175. // new polygons can only be added to root node; children can only be splitted polygons
  4176. throw new Error("Assertion failed");
  4177. var _this = this;
  4178. polygons.map(function(polygon) {
  4179. _this.addChild(polygon);
  4180. });
  4181. },
  4182. // remove a node
  4183. // - the siblings become toplevel nodes
  4184. // - the parent is removed recursively
  4185. remove: function() {
  4186. if (!this.removed) {
  4187. this.removed = true;
  4188. if (_CSGDEBUG) {
  4189. if (this.isRootNode()) throw new Error("Assertion failed"); // can't remove root node
  4190. if (this.children.length) throw new Error("Assertion failed"); // we shouldn't remove nodes with children
  4191. }
  4192. // remove ourselves from the parent's children list:
  4193. var parentschildren = this.parent.children;
  4194. var i = parentschildren.indexOf(this);
  4195. if (i < 0) throw new Error("Assertion failed");
  4196. parentschildren.splice(i, 1);
  4197. // invalidate the parent's polygon, and of all parents above it:
  4198. this.parent.recursivelyInvalidatePolygon();
  4199. }
  4200. },
  4201. isRemoved: function() {
  4202. return this.removed;
  4203. },
  4204. isRootNode: function() {
  4205. return !this.parent;
  4206. },
  4207. // invert all polygons in the tree. Call on the root node
  4208. invert: function() {
  4209. if (!this.isRootNode()) throw new Error("Assertion failed"); // can only call this on the root node
  4210. this.invertSub();
  4211. },
  4212. getPolygon: function() {
  4213. if (!this.polygon) throw new Error("Assertion failed"); // doesn't have a polygon, which means that it has been broken down
  4214. return this.polygon;
  4215. },
  4216. getPolygons: function(result) {
  4217. var children = [this];
  4218. var queue = [children];
  4219. var i, j, l, node;
  4220. for (i = 0; i < queue.length; ++i ) { // queue size can change in loop, don't cache length
  4221. children = queue[i];
  4222. for (j = 0, l = children.length; j < l; j++) { // ok to cache length
  4223. node = children[j];
  4224. if (node.polygon) {
  4225. // the polygon hasn't been broken yet. We can ignore the children and return our polygon:
  4226. result.push(node.polygon);
  4227. } else {
  4228. // our polygon has been split up and broken, so gather all subpolygons from the children
  4229. queue.push(node.children);
  4230. }
  4231. }
  4232. }
  4233. },
  4234. // split the node by a plane; add the resulting nodes to the frontnodes and backnodes array
  4235. // If the plane doesn't intersect the polygon, the 'this' object is added to one of the arrays
  4236. // If the plane does intersect the polygon, two new child nodes are created for the front and back fragments,
  4237. // and added to both arrays.
  4238. splitByPlane: function(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes) {
  4239. if (this.children.length) {
  4240. var queue = [this.children], i, j, l, node, nodes;
  4241. for (i = 0; i < queue.length; i++) { // queue.length can increase, do not cache
  4242. nodes = queue[i];
  4243. for (j = 0, l = nodes.length; j < l; j++) { // ok to cache length
  4244. node = nodes[j];
  4245. if (node.children.length) {
  4246. queue.push(node.children);
  4247. } else {
  4248. // no children. Split the polygon:
  4249. node._splitByPlane(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes);
  4250. }
  4251. }
  4252. }
  4253. } else {
  4254. this._splitByPlane(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes);
  4255. }
  4256. },
  4257. // only to be called for nodes with no children
  4258. _splitByPlane: function (plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes) {
  4259. var polygon = this.polygon;
  4260. if (polygon) {
  4261. var bound = polygon.boundingSphere();
  4262. var sphereradius = bound[1] + 1e-4;
  4263. var planenormal = plane.normal;
  4264. var spherecenter = bound[0];
  4265. var d = planenormal.dot(spherecenter) - plane.w;
  4266. if (d > sphereradius) {
  4267. frontnodes.push(this);
  4268. } else if (d < -sphereradius) {
  4269. backnodes.push(this);
  4270. } else {
  4271. var splitresult = plane.splitPolygon(polygon);
  4272. switch (splitresult.type) {
  4273. case 0:
  4274. // coplanar front:
  4275. coplanarfrontnodes.push(this);
  4276. break;
  4277. case 1:
  4278. // coplanar back:
  4279. coplanarbacknodes.push(this);
  4280. break;
  4281. case 2:
  4282. // front:
  4283. frontnodes.push(this);
  4284. break;
  4285. case 3:
  4286. // back:
  4287. backnodes.push(this);
  4288. break;
  4289. case 4:
  4290. // spanning:
  4291. if (splitresult.front) {
  4292. var frontnode = this.addChild(splitresult.front);
  4293. frontnodes.push(frontnode);
  4294. }
  4295. if (splitresult.back) {
  4296. var backnode = this.addChild(splitresult.back);
  4297. backnodes.push(backnode);
  4298. }
  4299. break;
  4300. }
  4301. }
  4302. }
  4303. },
  4304. // PRIVATE methods from here:
  4305. // add child to a node
  4306. // this should be called whenever the polygon is split
  4307. // a child should be created for every fragment of the split polygon
  4308. // returns the newly created child
  4309. addChild: function(polygon) {
  4310. var newchild = new CSG.PolygonTreeNode();
  4311. newchild.parent = this;
  4312. newchild.polygon = polygon;
  4313. this.children.push(newchild);
  4314. return newchild;
  4315. },
  4316. invertSub: function() {
  4317. var children = [this];
  4318. var queue = [children];
  4319. var i, j, l, node;
  4320. for (i = 0; i < queue.length; i++) {
  4321. children = queue[i];
  4322. for (j = 0, l = children.length; j < l; j++) {
  4323. node = children[j];
  4324. if (node.polygon) {
  4325. node.polygon = node.polygon.flipped();
  4326. }
  4327. queue.push(node.children);
  4328. }
  4329. }
  4330. },
  4331. recursivelyInvalidatePolygon: function() {
  4332. var node = this;
  4333. while (node.polygon) {
  4334. node.polygon = null;
  4335. if (node.parent) {
  4336. node = node.parent;
  4337. }
  4338. }
  4339. }
  4340. };
  4341. // # class Tree
  4342. // This is the root of a BSP tree
  4343. // We are using this separate class for the root of the tree, to hold the PolygonTreeNode root
  4344. // The actual tree is kept in this.rootnode
  4345. CSG.Tree = function(polygons) {
  4346. this.polygonTree = new CSG.PolygonTreeNode();
  4347. this.rootnode = new CSG.Node(null);
  4348. if (polygons) this.addPolygons(polygons);
  4349. };
  4350. CSG.Tree.prototype = {
  4351. invert: function() {
  4352. this.polygonTree.invert();
  4353. this.rootnode.invert();
  4354. },
  4355. // Remove all polygons in this BSP tree that are inside the other BSP tree
  4356. // `tree`.
  4357. clipTo: function(tree, alsoRemovecoplanarFront) {
  4358. alsoRemovecoplanarFront = alsoRemovecoplanarFront ? true : false;
  4359. this.rootnode.clipTo(tree, alsoRemovecoplanarFront);
  4360. },
  4361. allPolygons: function() {
  4362. var result = [];
  4363. this.polygonTree.getPolygons(result);
  4364. return result;
  4365. },
  4366. addPolygons: function(polygons) {
  4367. var _this = this;
  4368. var polygontreenodes = polygons.map(function(p) {
  4369. return _this.polygonTree.addChild(p);
  4370. });
  4371. this.rootnode.addPolygonTreeNodes(polygontreenodes);
  4372. }
  4373. };
  4374. // # class Node
  4375. // Holds a node in a BSP tree. A BSP tree is built from a collection of polygons
  4376. // by picking a polygon to split along.
  4377. // Polygons are not stored directly in the tree, but in PolygonTreeNodes, stored in
  4378. // this.polygontreenodes. Those PolygonTreeNodes are children of the owning
  4379. // CSG.Tree.polygonTree
  4380. // This is not a leafy BSP tree since there is
  4381. // no distinction between internal and leaf nodes.
  4382. CSG.Node = function(parent) {
  4383. this.plane = null;
  4384. this.front = null;
  4385. this.back = null;
  4386. this.polygontreenodes = [];
  4387. this.parent = parent;
  4388. };
  4389. CSG.Node.prototype = {
  4390. // Convert solid space to empty space and empty space to solid space.
  4391. invert: function() {
  4392. var queue = [this];
  4393. var i, node;
  4394. for (var i = 0; i < queue.length; i++) {
  4395. node = queue[i];
  4396. if(node.plane) node.plane = node.plane.flipped();
  4397. if(node.front) queue.push(node.front);
  4398. if(node.back) queue.push(node.back);
  4399. var temp = node.front;
  4400. node.front = node.back;
  4401. node.back = temp;
  4402. }
  4403. },
  4404. // clip polygontreenodes to our plane
  4405. // calls remove() for all clipped PolygonTreeNodes
  4406. clipPolygons: function(polygontreenodes, alsoRemovecoplanarFront) {
  4407. var args = {'node': this, 'polygontreenodes': polygontreenodes }
  4408. var node;
  4409. var stack = [];
  4410. do {
  4411. node = args.node;
  4412. polygontreenodes = args.polygontreenodes;
  4413. // begin "function"
  4414. if(node.plane) {
  4415. var backnodes = [];
  4416. var frontnodes = [];
  4417. var coplanarfrontnodes = alsoRemovecoplanarFront ? backnodes : frontnodes;
  4418. var plane = node.plane;
  4419. var numpolygontreenodes = polygontreenodes.length;
  4420. for(i = 0; i < numpolygontreenodes; i++) {
  4421. var node1 = polygontreenodes[i];
  4422. if(!node1.isRemoved()) {
  4423. node1.splitByPlane(plane, coplanarfrontnodes, backnodes, frontnodes, backnodes);
  4424. }
  4425. }
  4426. if(node.front && (frontnodes.length > 0)) {
  4427. stack.push({'node': node.front, 'polygontreenodes': frontnodes});
  4428. }
  4429. var numbacknodes = backnodes.length;
  4430. if (node.back && (numbacknodes > 0)) {
  4431. stack.push({'node': node.back, 'polygontreenodes': backnodes});
  4432. } else {
  4433. // there's nothing behind this plane. Delete the nodes behind this plane:
  4434. for (var i = 0; i < numbacknodes; i++) {
  4435. backnodes[i].remove();
  4436. }
  4437. }
  4438. }
  4439. args = stack.pop();
  4440. } while (typeof(args) !== 'undefined');
  4441. },
  4442. // Remove all polygons in this BSP tree that are inside the other BSP tree
  4443. // `tree`.
  4444. clipTo: function(tree, alsoRemovecoplanarFront) {
  4445. var node = this, stack = [];
  4446. do {
  4447. if(node.polygontreenodes.length > 0) {
  4448. tree.rootnode.clipPolygons(node.polygontreenodes, alsoRemovecoplanarFront);
  4449. }
  4450. if(node.front) stack.push(node.front);
  4451. if(node.back) stack.push(node.back);
  4452. node = stack.pop();
  4453. } while(typeof(node) !== 'undefined');
  4454. },
  4455. addPolygonTreeNodes: function(polygontreenodes) {
  4456. var args = {'node': this, 'polygontreenodes': polygontreenodes };
  4457. var node;
  4458. var stack = [];
  4459. do {
  4460. node = args.node;
  4461. polygontreenodes = args.polygontreenodes;
  4462. if (polygontreenodes.length === 0) {
  4463. args = stack.pop();
  4464. continue;
  4465. }
  4466. var _this = node;
  4467. if (!node.plane) {
  4468. var bestplane = polygontreenodes[0].getPolygon().plane;
  4469. node.plane = bestplane;
  4470. }
  4471. var frontnodes = [];
  4472. var backnodes = [];
  4473. for (var i = 0, n = polygontreenodes.length ; i < n; ++i) {
  4474. polygontreenodes[i].splitByPlane(_this.plane, _this.polygontreenodes, backnodes, frontnodes, backnodes);
  4475. }
  4476. if (frontnodes.length > 0) {
  4477. if (!node.front) node.front = new CSG.Node(node);
  4478. stack.push({'node': node.front, 'polygontreenodes': frontnodes});
  4479. }
  4480. if (backnodes.length > 0) {
  4481. if (!node.back) node.back = new CSG.Node(node);
  4482. stack.push({'node': node.back, 'polygontreenodes': backnodes});
  4483. }
  4484. args = stack.pop();
  4485. } while (typeof(args) !== 'undefined');
  4486. },
  4487. getParentPlaneNormals: function(normals, maxdepth) {
  4488. if (maxdepth > 0) {
  4489. if (this.parent) {
  4490. normals.push(this.parent.plane.normal);
  4491. this.parent.getParentPlaneNormals(normals, maxdepth - 1);
  4492. }
  4493. }
  4494. }
  4495. };
  4496. //////////
  4497. // # class Matrix4x4:
  4498. // Represents a 4x4 matrix. Elements are specified in row order
  4499. CSG.Matrix4x4 = function(elements) {
  4500. if (arguments.length >= 1) {
  4501. this.elements = elements;
  4502. } else {
  4503. // if no arguments passed: create unity matrix
  4504. this.elements = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
  4505. }
  4506. };
  4507. CSG.Matrix4x4.prototype = {
  4508. plus: function(m) {
  4509. var r = [];
  4510. for (var i = 0; i < 16; i++) {
  4511. r[i] = this.elements[i] + m.elements[i];
  4512. }
  4513. return new CSG.Matrix4x4(r);
  4514. },
  4515. minus: function(m) {
  4516. var r = [];
  4517. for (var i = 0; i < 16; i++) {
  4518. r[i] = this.elements[i] - m.elements[i];
  4519. }
  4520. return new CSG.Matrix4x4(r);
  4521. },
  4522. // right multiply by another 4x4 matrix:
  4523. multiply: function(m) {
  4524. // cache elements in local variables, for speedup:
  4525. var this0 = this.elements[0];
  4526. var this1 = this.elements[1];
  4527. var this2 = this.elements[2];
  4528. var this3 = this.elements[3];
  4529. var this4 = this.elements[4];
  4530. var this5 = this.elements[5];
  4531. var this6 = this.elements[6];
  4532. var this7 = this.elements[7];
  4533. var this8 = this.elements[8];
  4534. var this9 = this.elements[9];
  4535. var this10 = this.elements[10];
  4536. var this11 = this.elements[11];
  4537. var this12 = this.elements[12];
  4538. var this13 = this.elements[13];
  4539. var this14 = this.elements[14];
  4540. var this15 = this.elements[15];
  4541. var m0 = m.elements[0];
  4542. var m1 = m.elements[1];
  4543. var m2 = m.elements[2];
  4544. var m3 = m.elements[3];
  4545. var m4 = m.elements[4];
  4546. var m5 = m.elements[5];
  4547. var m6 = m.elements[6];
  4548. var m7 = m.elements[7];
  4549. var m8 = m.elements[8];
  4550. var m9 = m.elements[9];
  4551. var m10 = m.elements[10];
  4552. var m11 = m.elements[11];
  4553. var m12 = m.elements[12];
  4554. var m13 = m.elements[13];
  4555. var m14 = m.elements[14];
  4556. var m15 = m.elements[15];
  4557. var result = [];
  4558. result[0] = this0 * m0 + this1 * m4 + this2 * m8 + this3 * m12;
  4559. result[1] = this0 * m1 + this1 * m5 + this2 * m9 + this3 * m13;
  4560. result[2] = this0 * m2 + this1 * m6 + this2 * m10 + this3 * m14;
  4561. result[3] = this0 * m3 + this1 * m7 + this2 * m11 + this3 * m15;
  4562. result[4] = this4 * m0 + this5 * m4 + this6 * m8 + this7 * m12;
  4563. result[5] = this4 * m1 + this5 * m5 + this6 * m9 + this7 * m13;
  4564. result[6] = this4 * m2 + this5 * m6 + this6 * m10 + this7 * m14;
  4565. result[7] = this4 * m3 + this5 * m7 + this6 * m11 + this7 * m15;
  4566. result[8] = this8 * m0 + this9 * m4 + this10 * m8 + this11 * m12;
  4567. result[9] = this8 * m1 + this9 * m5 + this10 * m9 + this11 * m13;
  4568. result[10] = this8 * m2 + this9 * m6 + this10 * m10 + this11 * m14;
  4569. result[11] = this8 * m3 + this9 * m7 + this10 * m11 + this11 * m15;
  4570. result[12] = this12 * m0 + this13 * m4 + this14 * m8 + this15 * m12;
  4571. result[13] = this12 * m1 + this13 * m5 + this14 * m9 + this15 * m13;
  4572. result[14] = this12 * m2 + this13 * m6 + this14 * m10 + this15 * m14;
  4573. result[15] = this12 * m3 + this13 * m7 + this14 * m11 + this15 * m15;
  4574. return new CSG.Matrix4x4(result);
  4575. },
  4576. clone: function() {
  4577. var elements = this.elements.map(function(p) {
  4578. return p;
  4579. });
  4580. return new CSG.Matrix4x4(elements);
  4581. },
  4582. // Right multiply the matrix by a CSG.Vector3D (interpreted as 3 row, 1 column)
  4583. // (result = M*v)
  4584. // Fourth element is taken as 1
  4585. rightMultiply1x3Vector: function(v) {
  4586. var v0 = v._x;
  4587. var v1 = v._y;
  4588. var v2 = v._z;
  4589. var v3 = 1;
  4590. var x = v0 * this.elements[0] + v1 * this.elements[1] + v2 * this.elements[2] + v3 * this.elements[3];
  4591. var y = v0 * this.elements[4] + v1 * this.elements[5] + v2 * this.elements[6] + v3 * this.elements[7];
  4592. var z = v0 * this.elements[8] + v1 * this.elements[9] + v2 * this.elements[10] + v3 * this.elements[11];
  4593. var w = v0 * this.elements[12] + v1 * this.elements[13] + v2 * this.elements[14] + v3 * this.elements[15];
  4594. // scale such that fourth element becomes 1:
  4595. if (w != 1) {
  4596. var invw = 1.0 / w;
  4597. x *= invw;
  4598. y *= invw;
  4599. z *= invw;
  4600. }
  4601. return new CSG.Vector3D(x, y, z);
  4602. },
  4603. // Multiply a CSG.Vector3D (interpreted as 3 column, 1 row) by this matrix
  4604. // (result = v*M)
  4605. // Fourth element is taken as 1
  4606. leftMultiply1x3Vector: function(v) {
  4607. var v0 = v._x;
  4608. var v1 = v._y;
  4609. var v2 = v._z;
  4610. var v3 = 1;
  4611. var x = v0 * this.elements[0] + v1 * this.elements[4] + v2 * this.elements[8] + v3 * this.elements[12];
  4612. var y = v0 * this.elements[1] + v1 * this.elements[5] + v2 * this.elements[9] + v3 * this.elements[13];
  4613. var z = v0 * this.elements[2] + v1 * this.elements[6] + v2 * this.elements[10] + v3 * this.elements[14];
  4614. var w = v0 * this.elements[3] + v1 * this.elements[7] + v2 * this.elements[11] + v3 * this.elements[15];
  4615. // scale such that fourth element becomes 1:
  4616. if (w != 1) {
  4617. var invw = 1.0 / w;
  4618. x *= invw;
  4619. y *= invw;
  4620. z *= invw;
  4621. }
  4622. return new CSG.Vector3D(x, y, z);
  4623. },
  4624. // Right multiply the matrix by a CSG.Vector2D (interpreted as 2 row, 1 column)
  4625. // (result = M*v)
  4626. // Fourth element is taken as 1
  4627. rightMultiply1x2Vector: function(v) {
  4628. var v0 = v.x;
  4629. var v1 = v.y;
  4630. var v2 = 0;
  4631. var v3 = 1;
  4632. var x = v0 * this.elements[0] + v1 * this.elements[1] + v2 * this.elements[2] + v3 * this.elements[3];
  4633. var y = v0 * this.elements[4] + v1 * this.elements[5] + v2 * this.elements[6] + v3 * this.elements[7];
  4634. var z = v0 * this.elements[8] + v1 * this.elements[9] + v2 * this.elements[10] + v3 * this.elements[11];
  4635. var w = v0 * this.elements[12] + v1 * this.elements[13] + v2 * this.elements[14] + v3 * this.elements[15];
  4636. // scale such that fourth element becomes 1:
  4637. if (w != 1) {
  4638. var invw = 1.0 / w;
  4639. x *= invw;
  4640. y *= invw;
  4641. z *= invw;
  4642. }
  4643. return new CSG.Vector2D(x, y);
  4644. },
  4645. // Multiply a CSG.Vector2D (interpreted as 2 column, 1 row) by this matrix
  4646. // (result = v*M)
  4647. // Fourth element is taken as 1
  4648. leftMultiply1x2Vector: function(v) {
  4649. var v0 = v.x;
  4650. var v1 = v.y;
  4651. var v2 = 0;
  4652. var v3 = 1;
  4653. var x = v0 * this.elements[0] + v1 * this.elements[4] + v2 * this.elements[8] + v3 * this.elements[12];
  4654. var y = v0 * this.elements[1] + v1 * this.elements[5] + v2 * this.elements[9] + v3 * this.elements[13];
  4655. var z = v0 * this.elements[2] + v1 * this.elements[6] + v2 * this.elements[10] + v3 * this.elements[14];
  4656. var w = v0 * this.elements[3] + v1 * this.elements[7] + v2 * this.elements[11] + v3 * this.elements[15];
  4657. // scale such that fourth element becomes 1:
  4658. if (w != 1) {
  4659. var invw = 1.0 / w;
  4660. x *= invw;
  4661. y *= invw;
  4662. z *= invw;
  4663. }
  4664. return new CSG.Vector2D(x, y);
  4665. },
  4666. // determine whether this matrix is a mirroring transformation
  4667. isMirroring: function() {
  4668. var u = new CSG.Vector3D(this.elements[0], this.elements[4], this.elements[8]);
  4669. var v = new CSG.Vector3D(this.elements[1], this.elements[5], this.elements[9]);
  4670. var w = new CSG.Vector3D(this.elements[2], this.elements[6], this.elements[10]);
  4671. // for a true orthogonal, non-mirrored base, u.cross(v) == w
  4672. // If they have an opposite direction then we are mirroring
  4673. var mirrorvalue = u.cross(v).dot(w);
  4674. var ismirror = (mirrorvalue < 0);
  4675. return ismirror;
  4676. }
  4677. };
  4678. // return the unity matrix
  4679. CSG.Matrix4x4.unity = function() {
  4680. return new CSG.Matrix4x4();
  4681. };
  4682. // Create a rotation matrix for rotating around the x axis
  4683. CSG.Matrix4x4.rotationX = function(degrees) {
  4684. var radians = degrees * Math.PI * (1.0 / 180.0);
  4685. var cos = Math.cos(radians);
  4686. var sin = Math.sin(radians);
  4687. var els = [
  4688. 1, 0, 0, 0, 0, cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1
  4689. ];
  4690. return new CSG.Matrix4x4(els);
  4691. };
  4692. // Create a rotation matrix for rotating around the y axis
  4693. CSG.Matrix4x4.rotationY = function(degrees) {
  4694. var radians = degrees * Math.PI * (1.0 / 180.0);
  4695. var cos = Math.cos(radians);
  4696. var sin = Math.sin(radians);
  4697. var els = [
  4698. cos, 0, -sin, 0, 0, 1, 0, 0, sin, 0, cos, 0, 0, 0, 0, 1
  4699. ];
  4700. return new CSG.Matrix4x4(els);
  4701. };
  4702. // Create a rotation matrix for rotating around the z axis
  4703. CSG.Matrix4x4.rotationZ = function(degrees) {
  4704. var radians = degrees * Math.PI * (1.0 / 180.0);
  4705. var cos = Math.cos(radians);
  4706. var sin = Math.sin(radians);
  4707. var els = [
  4708. cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1
  4709. ];
  4710. return new CSG.Matrix4x4(els);
  4711. };
  4712. // Matrix for rotation about arbitrary point and axis
  4713. CSG.Matrix4x4.rotation = function(rotationCenter, rotationAxis, degrees) {
  4714. rotationCenter = new CSG.Vector3D(rotationCenter);
  4715. rotationAxis = new CSG.Vector3D(rotationAxis);
  4716. var rotationPlane = CSG.Plane.fromNormalAndPoint(rotationAxis, rotationCenter);
  4717. var orthobasis = new CSG.OrthoNormalBasis(rotationPlane);
  4718. var transformation = CSG.Matrix4x4.translation(rotationCenter.negated());
  4719. transformation = transformation.multiply(orthobasis.getProjectionMatrix());
  4720. transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(degrees));
  4721. transformation = transformation.multiply(orthobasis.getInverseProjectionMatrix());
  4722. transformation = transformation.multiply(CSG.Matrix4x4.translation(rotationCenter));
  4723. return transformation;
  4724. };
  4725. // Create an affine matrix for translation:
  4726. CSG.Matrix4x4.translation = function(v) {
  4727. // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D
  4728. var vec = new CSG.Vector3D(v);
  4729. var els = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, vec.x, vec.y, vec.z, 1];
  4730. return new CSG.Matrix4x4(els);
  4731. };
  4732. // Create an affine matrix for mirroring into an arbitrary plane:
  4733. CSG.Matrix4x4.mirroring = function(plane) {
  4734. var nx = plane.normal.x;
  4735. var ny = plane.normal.y;
  4736. var nz = plane.normal.z;
  4737. var w = plane.w;
  4738. var els = [
  4739. (1.0 - 2.0 * nx * nx), (-2.0 * ny * nx), (-2.0 * nz * nx), 0,
  4740. (-2.0 * nx * ny), (1.0 - 2.0 * ny * ny), (-2.0 * nz * ny), 0,
  4741. (-2.0 * nx * nz), (-2.0 * ny * nz), (1.0 - 2.0 * nz * nz), 0,
  4742. (2.0 * nx * w), (2.0 * ny * w), (2.0 * nz * w), 1
  4743. ];
  4744. return new CSG.Matrix4x4(els);
  4745. };
  4746. // Create an affine matrix for scaling:
  4747. CSG.Matrix4x4.scaling = function(v) {
  4748. // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D
  4749. var vec = new CSG.Vector3D(v);
  4750. var els = [
  4751. vec.x, 0, 0, 0, 0, vec.y, 0, 0, 0, 0, vec.z, 0, 0, 0, 0, 1
  4752. ];
  4753. return new CSG.Matrix4x4(els);
  4754. };
  4755. ///////////////////////////////////////////////////
  4756. // # class Vector2D:
  4757. // Represents a 2 element vector
  4758. CSG.Vector2D = function(x, y) {
  4759. if (arguments.length == 2) {
  4760. this._x = parseFloat(x);
  4761. this._y = parseFloat(y);
  4762. } else {
  4763. var ok = true;
  4764. if (arguments.length == 1) {
  4765. if (typeof(x) == "object") {
  4766. if (x instanceof CSG.Vector2D) {
  4767. this._x = x._x;
  4768. this._y = x._y;
  4769. } else if (x instanceof Array) {
  4770. this._x = parseFloat(x[0]);
  4771. this._y = parseFloat(x[1]);
  4772. } else if (('x' in x) && ('y' in x)) {
  4773. this._x = parseFloat(x.x);
  4774. this._y = parseFloat(x.y);
  4775. } else ok = false;
  4776. } else {
  4777. var v = parseFloat(x);
  4778. this._x = v;
  4779. this._y = v;
  4780. }
  4781. } else ok = false;
  4782. if (ok) {
  4783. if ((!CSG.IsFloat(this._x)) || (!CSG.IsFloat(this._y))) ok = false;
  4784. }
  4785. if (!ok) {
  4786. throw new Error("wrong arguments");
  4787. }
  4788. }
  4789. };
  4790. CSG.Vector2D.fromAngle = function(radians) {
  4791. return CSG.Vector2D.fromAngleRadians(radians);
  4792. };
  4793. CSG.Vector2D.fromAngleDegrees = function(degrees) {
  4794. var radians = Math.PI * degrees / 180;
  4795. return CSG.Vector2D.fromAngleRadians(radians);
  4796. };
  4797. CSG.Vector2D.fromAngleRadians = function(radians) {
  4798. return CSG.Vector2D.Create(Math.cos(radians), Math.sin(radians));
  4799. };
  4800. // This does the same as new CSG.Vector2D(x,y) but it doesn't go through the constructor
  4801. // and the parameters are not validated. Is much faster.
  4802. CSG.Vector2D.Create = function(x, y) {
  4803. var result = Object.create(CSG.Vector2D.prototype);
  4804. result._x = x;
  4805. result._y = y;
  4806. return result;
  4807. };
  4808. CSG.Vector2D.prototype = {
  4809. get x() {
  4810. return this._x;
  4811. },
  4812. get y() {
  4813. return this._y;
  4814. },
  4815. set x(v) {
  4816. throw new Error("Vector2D is immutable");
  4817. },
  4818. set y(v) {
  4819. throw new Error("Vector2D is immutable");
  4820. },
  4821. // extend to a 3D vector by adding a z coordinate:
  4822. toVector3D: function(z) {
  4823. return new CSG.Vector3D(this._x, this._y, z);
  4824. },
  4825. equals: function(a) {
  4826. return (this._x == a._x) && (this._y == a._y);
  4827. },
  4828. clone: function() {
  4829. return CSG.Vector2D.Create(this._x, this._y);
  4830. },
  4831. negated: function() {
  4832. return CSG.Vector2D.Create(-this._x, -this._y);
  4833. },
  4834. plus: function(a) {
  4835. return CSG.Vector2D.Create(this._x + a._x, this._y + a._y);
  4836. },
  4837. minus: function(a) {
  4838. return CSG.Vector2D.Create(this._x - a._x, this._y - a._y);
  4839. },
  4840. times: function(a) {
  4841. return CSG.Vector2D.Create(this._x * a, this._y * a);
  4842. },
  4843. dividedBy: function(a) {
  4844. return CSG.Vector2D.Create(this._x / a, this._y / a);
  4845. },
  4846. dot: function(a) {
  4847. return this._x * a._x + this._y * a._y;
  4848. },
  4849. lerp: function(a, t) {
  4850. return this.plus(a.minus(this).times(t));
  4851. },
  4852. length: function() {
  4853. return Math.sqrt(this.dot(this));
  4854. },
  4855. distanceTo: function(a) {
  4856. return this.minus(a).length();
  4857. },
  4858. distanceToSquared: function(a) {
  4859. return this.minus(a).lengthSquared();
  4860. },
  4861. lengthSquared: function() {
  4862. return this.dot(this);
  4863. },
  4864. unit: function() {
  4865. return this.dividedBy(this.length());
  4866. },
  4867. cross: function(a) {
  4868. return this._x * a._y - this._y * a._x;
  4869. },
  4870. // returns the vector rotated by 90 degrees clockwise
  4871. normal: function() {
  4872. return CSG.Vector2D.Create(this._y, -this._x);
  4873. },
  4874. // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector)
  4875. // Returns a new CSG.Vector2D
  4876. multiply4x4: function(matrix4x4) {
  4877. return matrix4x4.leftMultiply1x2Vector(this);
  4878. },
  4879. transform: function(matrix4x4) {
  4880. return matrix4x4.leftMultiply1x2Vector(this);
  4881. },
  4882. angle: function() {
  4883. return this.angleRadians();
  4884. },
  4885. angleDegrees: function() {
  4886. var radians = this.angleRadians();
  4887. return 180 * radians / Math.PI;
  4888. },
  4889. angleRadians: function() {
  4890. // y=sin, x=cos
  4891. return Math.atan2(this._y, this._x);
  4892. },
  4893. min: function(p) {
  4894. return CSG.Vector2D.Create(
  4895. Math.min(this._x, p._x), Math.min(this._y, p._y));
  4896. },
  4897. max: function(p) {
  4898. return CSG.Vector2D.Create(
  4899. Math.max(this._x, p._x), Math.max(this._y, p._y));
  4900. },
  4901. toString: function() {
  4902. return "(" + this._x.toFixed(2) + ", " + this._y.toFixed(2) + ")";
  4903. },
  4904. abs: function() {
  4905. return CSG.Vector2D.Create(Math.abs(this._x), Math.abs(this._y));
  4906. },
  4907. };
  4908. // # class Line2D
  4909. // Represents a directional line in 2D space
  4910. // A line is parametrized by its normal vector (perpendicular to the line, rotated 90 degrees counter clockwise)
  4911. // and w. The line passes through the point <normal>.times(w).
  4912. // normal must be a unit vector!
  4913. // Equation: p is on line if normal.dot(p)==w
  4914. CSG.Line2D = function(normal, w) {
  4915. normal = new CSG.Vector2D(normal);
  4916. w = parseFloat(w);
  4917. var l = normal.length();
  4918. // normalize:
  4919. w *= l;
  4920. normal = normal.times(1.0 / l);
  4921. this.normal = normal;
  4922. this.w = w;
  4923. };
  4924. CSG.Line2D.fromPoints = function(p1, p2) {
  4925. p1 = new CSG.Vector2D(p1);
  4926. p2 = new CSG.Vector2D(p2);
  4927. var direction = p2.minus(p1);
  4928. var normal = direction.normal().negated().unit();
  4929. var w = p1.dot(normal);
  4930. return new CSG.Line2D(normal, w);
  4931. };
  4932. CSG.Line2D.prototype = {
  4933. // same line but opposite direction:
  4934. reverse: function() {
  4935. return new CSG.Line2D(this.normal.negated(), -this.w);
  4936. },
  4937. equals: function(l) {
  4938. return (l.normal.equals(this.normal) && (l.w == this.w));
  4939. },
  4940. origin: function() {
  4941. return this.normal.times(this.w);
  4942. },
  4943. direction: function() {
  4944. return this.normal.normal();
  4945. },
  4946. xAtY: function(y) {
  4947. // (py == y) && (normal * p == w)
  4948. // -> px = (w - normal._y * y) / normal.x
  4949. var x = (this.w - this.normal._y * y) / this.normal.x;
  4950. return x;
  4951. },
  4952. absDistanceToPoint: function(point) {
  4953. point = new CSG.Vector2D(point);
  4954. var point_projected = point.dot(this.normal);
  4955. var distance = Math.abs(point_projected - this.w);
  4956. return distance;
  4957. },
  4958. /*FIXME: has error - origin is not defined, the method is never used
  4959. closestPoint: function(point) {
  4960. point = new CSG.Vector2D(point);
  4961. var vector = point.dot(this.direction());
  4962. return origin.plus(vector);
  4963. },
  4964. */
  4965. // intersection between two lines, returns point as Vector2D
  4966. intersectWithLine: function(line2d) {
  4967. var point = CSG.solve2Linear(this.normal.x, this.normal.y, line2d.normal.x, line2d.normal.y, this.w, line2d.w);
  4968. point = new CSG.Vector2D(point); // make vector2d
  4969. return point;
  4970. },
  4971. transform: function(matrix4x4) {
  4972. var origin = new CSG.Vector2D(0, 0);
  4973. var pointOnPlane = this.normal.times(this.w);
  4974. var neworigin = origin.multiply4x4(matrix4x4);
  4975. var neworiginPlusNormal = this.normal.multiply4x4(matrix4x4);
  4976. var newnormal = neworiginPlusNormal.minus(neworigin);
  4977. var newpointOnPlane = pointOnPlane.multiply4x4(matrix4x4);
  4978. var neww = newnormal.dot(newpointOnPlane);
  4979. return new CSG.Line2D(newnormal, neww);
  4980. }
  4981. };
  4982. // # class Line3D
  4983. // Represents a line in 3D space
  4984. // direction must be a unit vector
  4985. // point is a random point on the line
  4986. CSG.Line3D = function(point, direction) {
  4987. point = new CSG.Vector3D(point);
  4988. direction = new CSG.Vector3D(direction);
  4989. this.point = point;
  4990. this.direction = direction.unit();
  4991. };
  4992. CSG.Line3D.fromPoints = function(p1, p2) {
  4993. p1 = new CSG.Vector3D(p1);
  4994. p2 = new CSG.Vector3D(p2);
  4995. var direction = p2.minus(p1);
  4996. return new CSG.Line3D(p1, direction);
  4997. };
  4998. CSG.Line3D.fromPlanes = function(p1, p2) {
  4999. var direction = p1.normal.cross(p2.normal);
  5000. var l = direction.length();
  5001. if (l < 1e-10) {
  5002. throw new Error("Parallel planes");
  5003. }
  5004. direction = direction.times(1.0 / l);
  5005. var mabsx = Math.abs(direction.x);
  5006. var mabsy = Math.abs(direction.y);
  5007. var mabsz = Math.abs(direction.z);
  5008. var origin;
  5009. if ((mabsx >= mabsy) && (mabsx >= mabsz)) {
  5010. // direction vector is mostly pointing towards x
  5011. // find a point p for which x is zero:
  5012. var r = CSG.solve2Linear(p1.normal.y, p1.normal.z, p2.normal.y, p2.normal.z, p1.w, p2.w);
  5013. origin = new CSG.Vector3D(0, r[0], r[1]);
  5014. } else if ((mabsy >= mabsx) && (mabsy >= mabsz)) {
  5015. // find a point p for which y is zero:
  5016. var r = CSG.solve2Linear(p1.normal.x, p1.normal.z, p2.normal.x, p2.normal.z, p1.w, p2.w);
  5017. origin = new CSG.Vector3D(r[0], 0, r[1]);
  5018. } else {
  5019. // find a point p for which z is zero:
  5020. var r = CSG.solve2Linear(p1.normal.x, p1.normal.y, p2.normal.x, p2.normal.y, p1.w, p2.w);
  5021. origin = new CSG.Vector3D(r[0], r[1], 0);
  5022. }
  5023. return new CSG.Line3D(origin, direction);
  5024. };
  5025. CSG.Line3D.prototype = {
  5026. intersectWithPlane: function(plane) {
  5027. // plane: plane.normal * p = plane.w
  5028. // line: p=line.point + labda * line.direction
  5029. var labda = (plane.w - plane.normal.dot(this.point)) / plane.normal.dot(this.direction);
  5030. var point = this.point.plus(this.direction.times(labda));
  5031. return point;
  5032. },
  5033. clone: function(line) {
  5034. return new CSG.Line3D(this.point.clone(), this.direction.clone());
  5035. },
  5036. reverse: function() {
  5037. return new CSG.Line3D(this.point.clone(), this.direction.negated());
  5038. },
  5039. transform: function(matrix4x4) {
  5040. var newpoint = this.point.multiply4x4(matrix4x4);
  5041. var pointPlusDirection = this.point.plus(this.direction);
  5042. var newPointPlusDirection = pointPlusDirection.multiply4x4(matrix4x4);
  5043. var newdirection = newPointPlusDirection.minus(newpoint);
  5044. return new CSG.Line3D(newpoint, newdirection);
  5045. },
  5046. closestPointOnLine: function(point) {
  5047. point = new CSG.Vector3D(point);
  5048. var t = point.minus(this.point).dot(this.direction) / this.direction.dot(this.direction);
  5049. var closestpoint = this.point.plus(this.direction.times(t));
  5050. return closestpoint;
  5051. },
  5052. distanceToPoint: function(point) {
  5053. point = new CSG.Vector3D(point);
  5054. var closestpoint = this.closestPointOnLine(point);
  5055. var distancevector = point.minus(closestpoint);
  5056. var distance = distancevector.length();
  5057. return distance;
  5058. },
  5059. equals: function(line3d) {
  5060. if (!this.direction.equals(line3d.direction)) return false;
  5061. var distance = this.distanceToPoint(line3d.point);
  5062. if (distance > 1e-8) return false;
  5063. return true;
  5064. }
  5065. };
  5066. // # class OrthoNormalBasis
  5067. // Reprojects points on a 3D plane onto a 2D plane
  5068. // or from a 2D plane back onto the 3D plane
  5069. CSG.OrthoNormalBasis = function(plane, rightvector) {
  5070. if (arguments.length < 2) {
  5071. // choose an arbitrary right hand vector, making sure it is somewhat orthogonal to the plane normal:
  5072. rightvector = plane.normal.randomNonParallelVector();
  5073. } else {
  5074. rightvector = new CSG.Vector3D(rightvector);
  5075. }
  5076. this.v = plane.normal.cross(rightvector).unit();
  5077. this.u = this.v.cross(plane.normal);
  5078. this.plane = plane;
  5079. this.planeorigin = plane.normal.times(plane.w);
  5080. };
  5081. // Get an orthonormal basis for the standard XYZ planes.
  5082. // Parameters: the names of two 3D axes. The 2d x axis will map to the first given 3D axis, the 2d y
  5083. // axis will map to the second.
  5084. // Prepend the axis with a "-" to invert the direction of this axis.
  5085. // For example: CSG.OrthoNormalBasis.GetCartesian("-Y","Z")
  5086. // will return an orthonormal basis where the 2d X axis maps to the 3D inverted Y axis, and
  5087. // the 2d Y axis maps to the 3D Z axis.
  5088. CSG.OrthoNormalBasis.GetCartesian = function(xaxisid, yaxisid) {
  5089. var axisid = xaxisid + "/" + yaxisid;
  5090. var planenormal, rightvector;
  5091. if (axisid == "X/Y") {
  5092. planenormal = [0, 0, 1];
  5093. rightvector = [1, 0, 0];
  5094. } else if (axisid == "Y/-X") {
  5095. planenormal = [0, 0, 1];
  5096. rightvector = [0, 1, 0];
  5097. } else if (axisid == "-X/-Y") {
  5098. planenormal = [0, 0, 1];
  5099. rightvector = [-1, 0, 0];
  5100. } else if (axisid == "-Y/X") {
  5101. planenormal = [0, 0, 1];
  5102. rightvector = [0, -1, 0];
  5103. } else if (axisid == "-X/Y") {
  5104. planenormal = [0, 0, -1];
  5105. rightvector = [-1, 0, 0];
  5106. } else if (axisid == "-Y/-X") {
  5107. planenormal = [0, 0, -1];
  5108. rightvector = [0, -1, 0];
  5109. } else if (axisid == "X/-Y") {
  5110. planenormal = [0, 0, -1];
  5111. rightvector = [1, 0, 0];
  5112. } else if (axisid == "Y/X") {
  5113. planenormal = [0, 0, -1];
  5114. rightvector = [0, 1, 0];
  5115. } else if (axisid == "X/Z") {
  5116. planenormal = [0, -1, 0];
  5117. rightvector = [1, 0, 0];
  5118. } else if (axisid == "Z/-X") {
  5119. planenormal = [0, -1, 0];
  5120. rightvector = [0, 0, 1];
  5121. } else if (axisid == "-X/-Z") {
  5122. planenormal = [0, -1, 0];
  5123. rightvector = [-1, 0, 0];
  5124. } else if (axisid == "-Z/X") {
  5125. planenormal = [0, -1, 0];
  5126. rightvector = [0, 0, -1];
  5127. } else if (axisid == "-X/Z") {
  5128. planenormal = [0, 1, 0];
  5129. rightvector = [-1, 0, 0];
  5130. } else if (axisid == "-Z/-X") {
  5131. planenormal = [0, 1, 0];
  5132. rightvector = [0, 0, -1];
  5133. } else if (axisid == "X/-Z") {
  5134. planenormal = [0, 1, 0];
  5135. rightvector = [1, 0, 0];
  5136. } else if (axisid == "Z/X") {
  5137. planenormal = [0, 1, 0];
  5138. rightvector = [0, 0, 1];
  5139. } else if (axisid == "Y/Z") {
  5140. planenormal = [1, 0, 0];
  5141. rightvector = [0, 1, 0];
  5142. } else if (axisid == "Z/-Y") {
  5143. planenormal = [1, 0, 0];
  5144. rightvector = [0, 0, 1];
  5145. } else if (axisid == "-Y/-Z") {
  5146. planenormal = [1, 0, 0];
  5147. rightvector = [0, -1, 0];
  5148. } else if (axisid == "-Z/Y") {
  5149. planenormal = [1, 0, 0];
  5150. rightvector = [0, 0, -1];
  5151. } else if (axisid == "-Y/Z") {
  5152. planenormal = [-1, 0, 0];
  5153. rightvector = [0, -1, 0];
  5154. } else if (axisid == "-Z/-Y") {
  5155. planenormal = [-1, 0, 0];
  5156. rightvector = [0, 0, -1];
  5157. } else if (axisid == "Y/-Z") {
  5158. planenormal = [-1, 0, 0];
  5159. rightvector = [0, 1, 0];
  5160. } else if (axisid == "Z/Y") {
  5161. planenormal = [-1, 0, 0];
  5162. rightvector = [0, 0, 1];
  5163. } else {
  5164. throw new Error("CSG.OrthoNormalBasis.GetCartesian: invalid combination of axis identifiers. Should pass two string arguments from [X,Y,Z,-X,-Y,-Z], being two different axes.");
  5165. }
  5166. return new CSG.OrthoNormalBasis(new CSG.Plane(new CSG.Vector3D(planenormal), 0), new CSG.Vector3D(rightvector));
  5167. };
  5168. /*
  5169. // test code for CSG.OrthoNormalBasis.GetCartesian()
  5170. CSG.OrthoNormalBasis.GetCartesian_Test=function() {
  5171. var axisnames=["X","Y","Z","-X","-Y","-Z"];
  5172. var axisvectors=[[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0], [0,0,-1]];
  5173. for(var axis1=0; axis1 < 3; axis1++) {
  5174. for(var axis1inverted=0; axis1inverted < 2; axis1inverted++) {
  5175. var axis1name=axisnames[axis1+3*axis1inverted];
  5176. var axis1vector=axisvectors[axis1+3*axis1inverted];
  5177. for(var axis2=0; axis2 < 3; axis2++) {
  5178. if(axis2 != axis1) {
  5179. for(var axis2inverted=0; axis2inverted < 2; axis2inverted++) {
  5180. var axis2name=axisnames[axis2+3*axis2inverted];
  5181. var axis2vector=axisvectors[axis2+3*axis2inverted];
  5182. var orthobasis=CSG.OrthoNormalBasis.GetCartesian(axis1name, axis2name);
  5183. var test1=orthobasis.to3D(new CSG.Vector2D([1,0]));
  5184. var test2=orthobasis.to3D(new CSG.Vector2D([0,1]));
  5185. var expected1=new CSG.Vector3D(axis1vector);
  5186. var expected2=new CSG.Vector3D(axis2vector);
  5187. var d1=test1.distanceTo(expected1);
  5188. var d2=test2.distanceTo(expected2);
  5189. if( (d1 > 0.01) || (d2 > 0.01) ) {
  5190. throw new Error("Wrong!");
  5191. }}}}}}
  5192. throw new Error("OK");
  5193. };
  5194. */
  5195. // The z=0 plane, with the 3D x and y vectors mapped to the 2D x and y vector
  5196. CSG.OrthoNormalBasis.Z0Plane = function() {
  5197. var plane = new CSG.Plane(new CSG.Vector3D([0, 0, 1]), 0);
  5198. return new CSG.OrthoNormalBasis(plane, new CSG.Vector3D([1, 0, 0]));
  5199. };
  5200. CSG.OrthoNormalBasis.prototype = {
  5201. getProjectionMatrix: function() {
  5202. return new CSG.Matrix4x4([
  5203. this.u.x, this.v.x, this.plane.normal.x, 0,
  5204. this.u.y, this.v.y, this.plane.normal.y, 0,
  5205. this.u.z, this.v.z, this.plane.normal.z, 0,
  5206. 0, 0, -this.plane.w, 1
  5207. ]);
  5208. },
  5209. getInverseProjectionMatrix: function() {
  5210. var p = this.plane.normal.times(this.plane.w);
  5211. return new CSG.Matrix4x4([
  5212. this.u.x, this.u.y, this.u.z, 0,
  5213. this.v.x, this.v.y, this.v.z, 0,
  5214. this.plane.normal.x, this.plane.normal.y, this.plane.normal.z, 0,
  5215. p.x, p.y, p.z, 1
  5216. ]);
  5217. },
  5218. to2D: function(vec3) {
  5219. return new CSG.Vector2D(vec3.dot(this.u), vec3.dot(this.v));
  5220. },
  5221. to3D: function(vec2) {
  5222. return this.planeorigin.plus(this.u.times(vec2.x)).plus(this.v.times(vec2.y));
  5223. },
  5224. line3Dto2D: function(line3d) {
  5225. var a = line3d.point;
  5226. var b = line3d.direction.plus(a);
  5227. var a2d = this.to2D(a);
  5228. var b2d = this.to2D(b);
  5229. return CSG.Line2D.fromPoints(a2d, b2d);
  5230. },
  5231. line2Dto3D: function(line2d) {
  5232. var a = line2d.origin();
  5233. var b = line2d.direction().plus(a);
  5234. var a3d = this.to3D(a);
  5235. var b3d = this.to3D(b);
  5236. return CSG.Line3D.fromPoints(a3d, b3d);
  5237. },
  5238. transform: function(matrix4x4) {
  5239. // todo: this may not work properly in case of mirroring
  5240. var newplane = this.plane.transform(matrix4x4);
  5241. var rightpoint_transformed = this.u.transform(matrix4x4);
  5242. var origin_transformed = new CSG.Vector3D(0, 0, 0).transform(matrix4x4);
  5243. var newrighthandvector = rightpoint_transformed.minus(origin_transformed);
  5244. var newbasis = new CSG.OrthoNormalBasis(newplane, newrighthandvector);
  5245. return newbasis;
  5246. }
  5247. };
  5248. function insertSorted(array, element, comparefunc) {
  5249. var leftbound = 0;
  5250. var rightbound = array.length;
  5251. while (rightbound > leftbound) {
  5252. var testindex = Math.floor((leftbound + rightbound) / 2);
  5253. var testelement = array[testindex];
  5254. var compareresult = comparefunc(element, testelement);
  5255. if (compareresult > 0) // element > testelement
  5256. {
  5257. leftbound = testindex + 1;
  5258. } else {
  5259. rightbound = testindex;
  5260. }
  5261. }
  5262. array.splice(leftbound, 0, element);
  5263. }
  5264. // Get the x coordinate of a point with a certain y coordinate, interpolated between two
  5265. // points (CSG.Vector2D).
  5266. // Interpolation is robust even if the points have the same y coordinate
  5267. CSG.interpolateBetween2DPointsForY = function(point1, point2, y) {
  5268. var f1 = y - point1.y;
  5269. var f2 = point2.y - point1.y;
  5270. if (f2 < 0) {
  5271. f1 = -f1;
  5272. f2 = -f2;
  5273. }
  5274. var t;
  5275. if (f1 <= 0) {
  5276. t = 0.0;
  5277. } else if (f1 >= f2) {
  5278. t = 1.0;
  5279. } else if (f2 < 1e-10) {
  5280. t = 0.5;
  5281. } else {
  5282. t = f1 / f2;
  5283. }
  5284. var result = point1.x + t * (point2.x - point1.x);
  5285. return result;
  5286. };
  5287. // Retesselation function for a set of coplanar polygons. See the introduction at the top of
  5288. // this file.
  5289. CSG.reTesselateCoplanarPolygons = function(sourcepolygons, destpolygons) {
  5290. var EPS = 1e-5;
  5291. var numpolygons = sourcepolygons.length;
  5292. if (numpolygons > 0) {
  5293. var plane = sourcepolygons[0].plane;
  5294. var shared = sourcepolygons[0].shared;
  5295. var orthobasis = new CSG.OrthoNormalBasis(plane);
  5296. var polygonvertices2d = []; // array of array of CSG.Vector2D
  5297. var polygontopvertexindexes = []; // array of indexes of topmost vertex per polygon
  5298. var topy2polygonindexes = {};
  5299. var ycoordinatetopolygonindexes = {};
  5300. var xcoordinatebins = {};
  5301. var ycoordinatebins = {};
  5302. // convert all polygon vertices to 2D
  5303. // Make a list of all encountered y coordinates
  5304. // And build a map of all polygons that have a vertex at a certain y coordinate:
  5305. var ycoordinateBinningFactor = 1.0 / EPS * 10;
  5306. for (var polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
  5307. var poly3d = sourcepolygons[polygonindex];
  5308. var vertices2d = [];
  5309. var numvertices = poly3d.vertices.length;
  5310. var minindex = -1;
  5311. if (numvertices > 0) {
  5312. var miny, maxy, maxindex;
  5313. for (var i = 0; i < numvertices; i++) {
  5314. var pos2d = orthobasis.to2D(poly3d.vertices[i].pos);
  5315. // perform binning of y coordinates: If we have multiple vertices very
  5316. // close to each other, give them the same y coordinate:
  5317. var ycoordinatebin = Math.floor(pos2d.y * ycoordinateBinningFactor);
  5318. var newy;
  5319. if (ycoordinatebin in ycoordinatebins) {
  5320. newy = ycoordinatebins[ycoordinatebin];
  5321. } else if (ycoordinatebin + 1 in ycoordinatebins) {
  5322. newy = ycoordinatebins[ycoordinatebin + 1];
  5323. } else if (ycoordinatebin - 1 in ycoordinatebins) {
  5324. newy = ycoordinatebins[ycoordinatebin - 1];
  5325. } else {
  5326. newy = pos2d.y;
  5327. ycoordinatebins[ycoordinatebin] = pos2d.y;
  5328. }
  5329. pos2d = CSG.Vector2D.Create(pos2d.x, newy);
  5330. vertices2d.push(pos2d);
  5331. var y = pos2d.y;
  5332. if ((i === 0) || (y < miny)) {
  5333. miny = y;
  5334. minindex = i;
  5335. }
  5336. if ((i === 0) || (y > maxy)) {
  5337. maxy = y;
  5338. maxindex = i;
  5339. }
  5340. if (!(y in ycoordinatetopolygonindexes)) {
  5341. ycoordinatetopolygonindexes[y] = {};
  5342. }
  5343. ycoordinatetopolygonindexes[y][polygonindex] = true;
  5344. }
  5345. if (miny >= maxy) {
  5346. // degenerate polygon, all vertices have same y coordinate. Just ignore it from now:
  5347. vertices2d = [];
  5348. numvertices = 0;
  5349. minindex = -1;
  5350. } else {
  5351. if (!(miny in topy2polygonindexes)) {
  5352. topy2polygonindexes[miny] = [];
  5353. }
  5354. topy2polygonindexes[miny].push(polygonindex);
  5355. }
  5356. } // if(numvertices > 0)
  5357. // reverse the vertex order:
  5358. vertices2d.reverse();
  5359. minindex = numvertices - minindex - 1;
  5360. polygonvertices2d.push(vertices2d);
  5361. polygontopvertexindexes.push(minindex);
  5362. }
  5363. var ycoordinates = [];
  5364. for (var ycoordinate in ycoordinatetopolygonindexes) ycoordinates.push(ycoordinate);
  5365. ycoordinates.sort(fnNumberSort);
  5366. // Now we will iterate over all y coordinates, from lowest to highest y coordinate
  5367. // activepolygons: source polygons that are 'active', i.e. intersect with our y coordinate
  5368. // Is sorted so the polygons are in left to right order
  5369. // Each element in activepolygons has these properties:
  5370. // polygonindex: the index of the source polygon (i.e. an index into the sourcepolygons
  5371. // and polygonvertices2d arrays)
  5372. // leftvertexindex: the index of the vertex at the left side of the polygon (lowest x)
  5373. // that is at or just above the current y coordinate
  5374. // rightvertexindex: dito at right hand side of polygon
  5375. // topleft, bottomleft: coordinates of the left side of the polygon crossing the current y coordinate
  5376. // topright, bottomright: coordinates of the right hand side of the polygon crossing the current y coordinate
  5377. var activepolygons = [];
  5378. var prevoutpolygonrow = [];
  5379. for (var yindex = 0; yindex < ycoordinates.length; yindex++) {
  5380. var newoutpolygonrow = [];
  5381. var ycoordinate_as_string = ycoordinates[yindex];
  5382. var ycoordinate = Number(ycoordinate_as_string);
  5383. // update activepolygons for this y coordinate:
  5384. // - Remove any polygons that end at this y coordinate
  5385. // - update leftvertexindex and rightvertexindex (which point to the current vertex index
  5386. // at the the left and right side of the polygon
  5387. // Iterate over all polygons that have a corner at this y coordinate:
  5388. var polygonindexeswithcorner = ycoordinatetopolygonindexes[ycoordinate_as_string];
  5389. for (var activepolygonindex = 0; activepolygonindex < activepolygons.length; ++activepolygonindex) {
  5390. var activepolygon = activepolygons[activepolygonindex];
  5391. var polygonindex = activepolygon.polygonindex;
  5392. if (polygonindexeswithcorner[polygonindex]) {
  5393. // this active polygon has a corner at this y coordinate:
  5394. var vertices2d = polygonvertices2d[polygonindex];
  5395. var numvertices = vertices2d.length;
  5396. var newleftvertexindex = activepolygon.leftvertexindex;
  5397. var newrightvertexindex = activepolygon.rightvertexindex;
  5398. // See if we need to increase leftvertexindex or decrease rightvertexindex:
  5399. while (true) {
  5400. var nextleftvertexindex = newleftvertexindex + 1;
  5401. if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0;
  5402. if (vertices2d[nextleftvertexindex].y != ycoordinate) break;
  5403. newleftvertexindex = nextleftvertexindex;
  5404. }
  5405. var nextrightvertexindex = newrightvertexindex - 1;
  5406. if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1;
  5407. if (vertices2d[nextrightvertexindex].y == ycoordinate) {
  5408. newrightvertexindex = nextrightvertexindex;
  5409. }
  5410. if ((newleftvertexindex != activepolygon.leftvertexindex) && (newleftvertexindex == newrightvertexindex)) {
  5411. // We have increased leftvertexindex or decreased rightvertexindex, and now they point to the same vertex
  5412. // This means that this is the bottom point of the polygon. We'll remove it:
  5413. activepolygons.splice(activepolygonindex, 1);
  5414. --activepolygonindex;
  5415. } else {
  5416. activepolygon.leftvertexindex = newleftvertexindex;
  5417. activepolygon.rightvertexindex = newrightvertexindex;
  5418. activepolygon.topleft = vertices2d[newleftvertexindex];
  5419. activepolygon.topright = vertices2d[newrightvertexindex];
  5420. var nextleftvertexindex = newleftvertexindex + 1;
  5421. if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0;
  5422. activepolygon.bottomleft = vertices2d[nextleftvertexindex];
  5423. var nextrightvertexindex = newrightvertexindex - 1;
  5424. if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1;
  5425. activepolygon.bottomright = vertices2d[nextrightvertexindex];
  5426. }
  5427. } // if polygon has corner here
  5428. } // for activepolygonindex
  5429. var nextycoordinate;
  5430. if (yindex >= ycoordinates.length - 1) {
  5431. // last row, all polygons must be finished here:
  5432. activepolygons = [];
  5433. nextycoordinate = null;
  5434. } else // yindex < ycoordinates.length-1
  5435. {
  5436. nextycoordinate = Number(ycoordinates[yindex + 1]);
  5437. var middleycoordinate = 0.5 * (ycoordinate + nextycoordinate);
  5438. // update activepolygons by adding any polygons that start here:
  5439. var startingpolygonindexes = topy2polygonindexes[ycoordinate_as_string];
  5440. for (var polygonindex_key in startingpolygonindexes) {
  5441. var polygonindex = startingpolygonindexes[polygonindex_key];
  5442. var vertices2d = polygonvertices2d[polygonindex];
  5443. var numvertices = vertices2d.length;
  5444. var topvertexindex = polygontopvertexindexes[polygonindex];
  5445. // the top of the polygon may be a horizontal line. In that case topvertexindex can point to any point on this line.
  5446. // Find the left and right topmost vertices which have the current y coordinate:
  5447. var topleftvertexindex = topvertexindex;
  5448. while (true) {
  5449. var i = topleftvertexindex + 1;
  5450. if (i >= numvertices) i = 0;
  5451. if (vertices2d[i].y != ycoordinate) break;
  5452. if (i == topvertexindex) break; // should not happen, but just to prevent endless loops
  5453. topleftvertexindex = i;
  5454. }
  5455. var toprightvertexindex = topvertexindex;
  5456. while (true) {
  5457. var i = toprightvertexindex - 1;
  5458. if (i < 0) i = numvertices - 1;
  5459. if (vertices2d[i].y != ycoordinate) break;
  5460. if (i == topleftvertexindex) break; // should not happen, but just to prevent endless loops
  5461. toprightvertexindex = i;
  5462. }
  5463. var nextleftvertexindex = topleftvertexindex + 1;
  5464. if (nextleftvertexindex >= numvertices) nextleftvertexindex = 0;
  5465. var nextrightvertexindex = toprightvertexindex - 1;
  5466. if (nextrightvertexindex < 0) nextrightvertexindex = numvertices - 1;
  5467. var newactivepolygon = {
  5468. polygonindex: polygonindex,
  5469. leftvertexindex: topleftvertexindex,
  5470. rightvertexindex: toprightvertexindex,
  5471. topleft: vertices2d[topleftvertexindex],
  5472. topright: vertices2d[toprightvertexindex],
  5473. bottomleft: vertices2d[nextleftvertexindex],
  5474. bottomright: vertices2d[nextrightvertexindex],
  5475. };
  5476. insertSorted(activepolygons, newactivepolygon, function(el1, el2) {
  5477. var x1 = CSG.interpolateBetween2DPointsForY(
  5478. el1.topleft, el1.bottomleft, middleycoordinate);
  5479. var x2 = CSG.interpolateBetween2DPointsForY(
  5480. el2.topleft, el2.bottomleft, middleycoordinate);
  5481. if (x1 > x2) return 1;
  5482. if (x1 < x2) return -1;
  5483. return 0;
  5484. });
  5485. } // for(var polygonindex in startingpolygonindexes)
  5486. } // yindex < ycoordinates.length-1
  5487. //if( (yindex == ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) )
  5488. if (true) {
  5489. // Now activepolygons is up to date
  5490. // Build the output polygons for the next row in newoutpolygonrow:
  5491. for (var activepolygon_key in activepolygons) {
  5492. var activepolygon = activepolygons[activepolygon_key];
  5493. var polygonindex = activepolygon.polygonindex;
  5494. var vertices2d = polygonvertices2d[polygonindex];
  5495. var numvertices = vertices2d.length;
  5496. var x = CSG.interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, ycoordinate);
  5497. var topleft = CSG.Vector2D.Create(x, ycoordinate);
  5498. x = CSG.interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, ycoordinate);
  5499. var topright = CSG.Vector2D.Create(x, ycoordinate);
  5500. x = CSG.interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, nextycoordinate);
  5501. var bottomleft = CSG.Vector2D.Create(x, nextycoordinate);
  5502. x = CSG.interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, nextycoordinate);
  5503. var bottomright = CSG.Vector2D.Create(x, nextycoordinate);
  5504. var outpolygon = {
  5505. topleft: topleft,
  5506. topright: topright,
  5507. bottomleft: bottomleft,
  5508. bottomright: bottomright,
  5509. leftline: CSG.Line2D.fromPoints(topleft, bottomleft),
  5510. rightline: CSG.Line2D.fromPoints(bottomright, topright)
  5511. };
  5512. if (newoutpolygonrow.length > 0) {
  5513. var prevoutpolygon = newoutpolygonrow[newoutpolygonrow.length - 1];
  5514. var d1 = outpolygon.topleft.distanceTo(prevoutpolygon.topright);
  5515. var d2 = outpolygon.bottomleft.distanceTo(prevoutpolygon.bottomright);
  5516. if ((d1 < EPS) && (d2 < EPS)) {
  5517. // we can join this polygon with the one to the left:
  5518. outpolygon.topleft = prevoutpolygon.topleft;
  5519. outpolygon.leftline = prevoutpolygon.leftline;
  5520. outpolygon.bottomleft = prevoutpolygon.bottomleft;
  5521. newoutpolygonrow.splice(newoutpolygonrow.length - 1, 1);
  5522. }
  5523. }
  5524. newoutpolygonrow.push(outpolygon);
  5525. } // for(activepolygon in activepolygons)
  5526. if (yindex > 0) {
  5527. // try to match the new polygons against the previous row:
  5528. var prevcontinuedindexes = {};
  5529. var matchedindexes = {};
  5530. for (var i = 0; i < newoutpolygonrow.length; i++) {
  5531. var thispolygon = newoutpolygonrow[i];
  5532. for (var ii = 0; ii < prevoutpolygonrow.length; ii++) {
  5533. if (!matchedindexes[ii]) // not already processed?
  5534. {
  5535. // We have a match if the sidelines are equal or if the top coordinates
  5536. // are on the sidelines of the previous polygon
  5537. var prevpolygon = prevoutpolygonrow[ii];
  5538. if (prevpolygon.bottomleft.distanceTo(thispolygon.topleft) < EPS) {
  5539. if (prevpolygon.bottomright.distanceTo(thispolygon.topright) < EPS) {
  5540. // Yes, the top of this polygon matches the bottom of the previous:
  5541. matchedindexes[ii] = true;
  5542. // Now check if the joined polygon would remain convex:
  5543. var d1 = thispolygon.leftline.direction().x - prevpolygon.leftline.direction().x;
  5544. var d2 = thispolygon.rightline.direction().x - prevpolygon.rightline.direction().x;
  5545. var leftlinecontinues = Math.abs(d1) < EPS;
  5546. var rightlinecontinues = Math.abs(d2) < EPS;
  5547. var leftlineisconvex = leftlinecontinues || (d1 >= 0);
  5548. var rightlineisconvex = rightlinecontinues || (d2 >= 0);
  5549. if (leftlineisconvex && rightlineisconvex) {
  5550. // yes, both sides have convex corners:
  5551. // This polygon will continue the previous polygon
  5552. thispolygon.outpolygon = prevpolygon.outpolygon;
  5553. thispolygon.leftlinecontinues = leftlinecontinues;
  5554. thispolygon.rightlinecontinues = rightlinecontinues;
  5555. prevcontinuedindexes[ii] = true;
  5556. }
  5557. break;
  5558. }
  5559. }
  5560. } // if(!prevcontinuedindexes[ii])
  5561. } // for ii
  5562. } // for i
  5563. for (var ii = 0; ii < prevoutpolygonrow.length; ii++) {
  5564. if (!prevcontinuedindexes[ii]) {
  5565. // polygon ends here
  5566. // Finish the polygon with the last point(s):
  5567. var prevpolygon = prevoutpolygonrow[ii];
  5568. prevpolygon.outpolygon.rightpoints.push(prevpolygon.bottomright);
  5569. if (prevpolygon.bottomright.distanceTo(prevpolygon.bottomleft) > EPS) {
  5570. // polygon ends with a horizontal line:
  5571. prevpolygon.outpolygon.leftpoints.push(prevpolygon.bottomleft);
  5572. }
  5573. // reverse the left half so we get a counterclockwise circle:
  5574. prevpolygon.outpolygon.leftpoints.reverse();
  5575. var points2d = prevpolygon.outpolygon.rightpoints.concat(prevpolygon.outpolygon.leftpoints);
  5576. var vertices3d = [];
  5577. points2d.map(function(point2d) {
  5578. var point3d = orthobasis.to3D(point2d);
  5579. var vertex3d = new CSG.Vertex(point3d);
  5580. vertices3d.push(vertex3d);
  5581. });
  5582. var polygon = new CSG.Polygon(vertices3d, shared, plane);
  5583. destpolygons.push(polygon);
  5584. }
  5585. }
  5586. } // if(yindex > 0)
  5587. for (var i = 0; i < newoutpolygonrow.length; i++) {
  5588. var thispolygon = newoutpolygonrow[i];
  5589. if (!thispolygon.outpolygon) {
  5590. // polygon starts here:
  5591. thispolygon.outpolygon = {
  5592. leftpoints: [],
  5593. rightpoints: []
  5594. };
  5595. thispolygon.outpolygon.leftpoints.push(thispolygon.topleft);
  5596. if (thispolygon.topleft.distanceTo(thispolygon.topright) > EPS) {
  5597. // we have a horizontal line at the top:
  5598. thispolygon.outpolygon.rightpoints.push(thispolygon.topright);
  5599. }
  5600. } else {
  5601. // continuation of a previous row
  5602. if (!thispolygon.leftlinecontinues) {
  5603. thispolygon.outpolygon.leftpoints.push(thispolygon.topleft);
  5604. }
  5605. if (!thispolygon.rightlinecontinues) {
  5606. thispolygon.outpolygon.rightpoints.push(thispolygon.topright);
  5607. }
  5608. }
  5609. }
  5610. prevoutpolygonrow = newoutpolygonrow;
  5611. }
  5612. } // for yindex
  5613. } // if(numpolygons > 0)
  5614. };
  5615. ////////////////////////////////
  5616. // ## class fuzzyFactory
  5617. // This class acts as a factory for objects. We can search for an object with approximately
  5618. // the desired properties (say a rectangle with width 2 and height 1)
  5619. // The lookupOrCreate() method looks for an existing object (for example it may find an existing rectangle
  5620. // with width 2.0001 and height 0.999. If no object is found, the user supplied callback is
  5621. // called, which should generate a new object. The new object is inserted into the database
  5622. // so it can be found by future lookupOrCreate() calls.
  5623. // Constructor:
  5624. // numdimensions: the number of parameters for each object
  5625. // for example for a 2D rectangle this would be 2
  5626. // tolerance: The maximum difference for each parameter allowed to be considered a match
  5627. CSG.fuzzyFactory = function(numdimensions, tolerance) {
  5628. this.lookuptable = {};
  5629. this.multiplier = 1.0 / tolerance;
  5630. };
  5631. CSG.fuzzyFactory.prototype = {
  5632. // var obj = f.lookupOrCreate([el1, el2, el3], function(elements) {/* create the new object */});
  5633. // Performs a fuzzy lookup of the object with the specified elements.
  5634. // If found, returns the existing object
  5635. // If not found, calls the supplied callback function which should create a new object with
  5636. // the specified properties. This object is inserted in the lookup database.
  5637. lookupOrCreate: function(els, creatorCallback) {
  5638. var hash = "";
  5639. var multiplier = this.multiplier;
  5640. els.forEach(function(el) {
  5641. var valueQuantized = Math.round(el * multiplier);
  5642. hash += valueQuantized + "/";
  5643. });
  5644. if (hash in this.lookuptable) {
  5645. return this.lookuptable[hash];
  5646. } else {
  5647. var object = creatorCallback(els);
  5648. var hashparts = els.map(function(el) {
  5649. var q0 = Math.floor(el * multiplier);
  5650. var q1 = q0 + 1;
  5651. return ["" + q0 + "/", "" + q1 + "/"];
  5652. });
  5653. var numelements = els.length;
  5654. var numhashes = 1 << numelements;
  5655. for (var hashmask = 0; hashmask < numhashes; ++hashmask) {
  5656. var hashmask_shifted = hashmask;
  5657. hash = "";
  5658. hashparts.forEach(function(hashpart) {
  5659. hash += hashpart[hashmask_shifted & 1];
  5660. hashmask_shifted >>= 1;
  5661. });
  5662. this.lookuptable[hash] = object;
  5663. }
  5664. return object;
  5665. }
  5666. },
  5667. };
  5668. //////////////////////////////////////
  5669. CSG.fuzzyCSGFactory = function() {
  5670. this.vertexfactory = new CSG.fuzzyFactory(3, 1e-5);
  5671. this.planefactory = new CSG.fuzzyFactory(4, 1e-5);
  5672. this.polygonsharedfactory = {};
  5673. };
  5674. CSG.fuzzyCSGFactory.prototype = {
  5675. getPolygonShared: function(sourceshared) {
  5676. var hash = sourceshared.getHash();
  5677. if (hash in this.polygonsharedfactory) {
  5678. return this.polygonsharedfactory[hash];
  5679. } else {
  5680. this.polygonsharedfactory[hash] = sourceshared;
  5681. return sourceshared;
  5682. }
  5683. },
  5684. getVertex: function(sourcevertex) {
  5685. var elements = [sourcevertex.pos._x, sourcevertex.pos._y, sourcevertex.pos._z];
  5686. var result = this.vertexfactory.lookupOrCreate(elements, function(els) {
  5687. return sourcevertex;
  5688. });
  5689. return result;
  5690. },
  5691. getPlane: function(sourceplane) {
  5692. var elements = [sourceplane.normal._x, sourceplane.normal._y, sourceplane.normal._z, sourceplane.w];
  5693. var result = this.planefactory.lookupOrCreate(elements, function(els) {
  5694. return sourceplane;
  5695. });
  5696. return result;
  5697. },
  5698. getPolygon: function(sourcepolygon) {
  5699. var newplane = this.getPlane(sourcepolygon.plane);
  5700. var newshared = this.getPolygonShared(sourcepolygon.shared);
  5701. var _this = this;
  5702. var newvertices = sourcepolygon.vertices.map(function(vertex) {
  5703. return _this.getVertex(vertex);
  5704. });
  5705. // two vertices that were originally very close may now have become
  5706. // truly identical (referring to the same CSG.Vertex object).
  5707. // Remove duplicate vertices:
  5708. var newvertices_dedup = [];
  5709. if(newvertices.length > 0) {
  5710. var prevvertextag = newvertices[newvertices.length-1].getTag();
  5711. newvertices.forEach(function(vertex) {
  5712. var vertextag = vertex.getTag();
  5713. if(vertextag != prevvertextag)
  5714. {
  5715. newvertices_dedup.push(vertex);
  5716. }
  5717. prevvertextag = vertextag;
  5718. });
  5719. }
  5720. // If it's degenerate, remove all vertices:
  5721. if(newvertices_dedup.length < 3) {
  5722. newvertices_dedup = [];
  5723. }
  5724. return new CSG.Polygon(newvertices_dedup, newshared, newplane);
  5725. },
  5726. getCSG: function(sourcecsg) {
  5727. var _this = this;
  5728. var newpolygons = [];
  5729. sourcecsg.polygons.forEach(function(polygon) {
  5730. var newpolygon = _this.getPolygon(polygon);
  5731. // see getPolygon above: we may get a polygon with no vertices, discard it:
  5732. if(newpolygon.vertices.length >= 3)
  5733. {
  5734. newpolygons.push(newpolygon);
  5735. }
  5736. });
  5737. return CSG.fromPolygons(newpolygons);
  5738. }
  5739. };
  5740. //////////////////////////////////////
  5741. // Tag factory: we can request a unique tag through CSG.getTag()
  5742. CSG.staticTag = 1;
  5743. CSG.getTag = function() {
  5744. return CSG.staticTag++;
  5745. };
  5746. //////////////////////////////////////
  5747. // # Class Properties
  5748. // This class is used to store properties of a solid
  5749. // A property can for example be a CSG.Vertex, a CSG.Plane or a CSG.Line3D
  5750. // Whenever an affine transform is applied to the CSG solid, all its properties are
  5751. // transformed as well.
  5752. // The properties can be stored in a complex nested structure (using arrays and objects)
  5753. CSG.Properties = function() {};
  5754. CSG.Properties.prototype = {
  5755. _transform: function(matrix4x4) {
  5756. var result = new CSG.Properties();
  5757. CSG.Properties.transformObj(this, result, matrix4x4);
  5758. return result;
  5759. },
  5760. _merge: function(otherproperties) {
  5761. var result = new CSG.Properties();
  5762. CSG.Properties.cloneObj(this, result);
  5763. CSG.Properties.addFrom(result, otherproperties);
  5764. return result;
  5765. }
  5766. };
  5767. CSG.Properties.transformObj = function(source, result, matrix4x4) {
  5768. for (var propertyname in source) {
  5769. if (propertyname == "_transform") continue;
  5770. if (propertyname == "_merge") continue;
  5771. var propertyvalue = source[propertyname];
  5772. var transformed = propertyvalue;
  5773. if (typeof(propertyvalue) == "object") {
  5774. if (('transform' in propertyvalue) && (typeof(propertyvalue.transform) == "function")) {
  5775. transformed = propertyvalue.transform(matrix4x4);
  5776. } else if (propertyvalue instanceof Array) {
  5777. transformed = [];
  5778. CSG.Properties.transformObj(propertyvalue, transformed, matrix4x4);
  5779. } else if (propertyvalue instanceof CSG.Properties) {
  5780. transformed = new CSG.Properties();
  5781. CSG.Properties.transformObj(propertyvalue, transformed, matrix4x4);
  5782. }
  5783. }
  5784. result[propertyname] = transformed;
  5785. }
  5786. };
  5787. CSG.Properties.cloneObj = function(source, result) {
  5788. for (var propertyname in source) {
  5789. if (propertyname == "_transform") continue;
  5790. if (propertyname == "_merge") continue;
  5791. var propertyvalue = source[propertyname];
  5792. var cloned = propertyvalue;
  5793. if (typeof(propertyvalue) == "object") {
  5794. if (propertyvalue instanceof Array) {
  5795. cloned = [];
  5796. for (var i = 0; i < propertyvalue.length; i++) {
  5797. cloned.push(propertyvalue[i]);
  5798. }
  5799. } else if (propertyvalue instanceof CSG.Properties) {
  5800. cloned = new CSG.Properties();
  5801. CSG.Properties.cloneObj(propertyvalue, cloned);
  5802. }
  5803. }
  5804. result[propertyname] = cloned;
  5805. }
  5806. };
  5807. CSG.Properties.addFrom = function(result, otherproperties) {
  5808. for (var propertyname in otherproperties) {
  5809. if (propertyname == "_transform") continue;
  5810. if (propertyname == "_merge") continue;
  5811. if ((propertyname in result) &&
  5812. (typeof(result[propertyname]) == "object") &&
  5813. (result[propertyname] instanceof CSG.Properties) &&
  5814. (typeof(otherproperties[propertyname]) == "object") &&
  5815. (otherproperties[propertyname] instanceof CSG.Properties)) {
  5816. CSG.Properties.addFrom(result[propertyname], otherproperties[propertyname]);
  5817. } else if (!(propertyname in result)) {
  5818. result[propertyname] = otherproperties[propertyname];
  5819. }
  5820. }
  5821. };
  5822. //////////////////////////////////////
  5823. // # class Connector
  5824. // A connector allows to attach two objects at predefined positions
  5825. // For example a servo motor and a servo horn:
  5826. // Both can have a Connector called 'shaft'
  5827. // The horn can be moved and rotated such that the two connectors match
  5828. // and the horn is attached to the servo motor at the proper position.
  5829. // Connectors are stored in the properties of a CSG solid so they are
  5830. // ge the same transformations applied as the solid
  5831. CSG.Connector = function(point, axisvector, normalvector) {
  5832. this.point = new CSG.Vector3D(point);
  5833. this.axisvector = new CSG.Vector3D(axisvector).unit();
  5834. this.normalvector = new CSG.Vector3D(normalvector).unit();
  5835. };
  5836. CSG.Connector.prototype = {
  5837. normalized: function() {
  5838. var axisvector = this.axisvector.unit();
  5839. // make the normal vector truly normal:
  5840. var n = this.normalvector.cross(axisvector).unit();
  5841. var normalvector = axisvector.cross(n);
  5842. return new CSG.Connector(this.point, axisvector, normalvector);
  5843. },
  5844. transform: function(matrix4x4) {
  5845. var point = this.point.multiply4x4(matrix4x4);
  5846. var axisvector = this.point.plus(this.axisvector).multiply4x4(matrix4x4).minus(point);
  5847. var normalvector = this.point.plus(this.normalvector).multiply4x4(matrix4x4).minus(point);
  5848. return new CSG.Connector(point, axisvector, normalvector);
  5849. },
  5850. // Get the transformation matrix to connect this Connector to another connector
  5851. // other: a CSG.Connector to which this connector should be connected
  5852. // mirror: false: the 'axis' vectors of the connectors should point in the same direction
  5853. // true: the 'axis' vectors of the connectors should point in opposite direction
  5854. // normalrotation: degrees of rotation between the 'normal' vectors of the two
  5855. // connectors
  5856. getTransformationTo: function(other, mirror, normalrotation) {
  5857. mirror = mirror ? true : false;
  5858. normalrotation = normalrotation ? Number(normalrotation) : 0;
  5859. var us = this.normalized();
  5860. other = other.normalized();
  5861. // shift to the origin:
  5862. var transformation = CSG.Matrix4x4.translation(this.point.negated());
  5863. // construct the plane crossing through the origin and the two axes:
  5864. var axesplane = CSG.Plane.anyPlaneFromVector3Ds(
  5865. new CSG.Vector3D(0, 0, 0), us.axisvector, other.axisvector);
  5866. var axesbasis = new CSG.OrthoNormalBasis(axesplane);
  5867. var angle1 = axesbasis.to2D(us.axisvector).angle();
  5868. var angle2 = axesbasis.to2D(other.axisvector).angle();
  5869. var rotation = 180.0 * (angle2 - angle1) / Math.PI;
  5870. if (mirror) rotation += 180.0;
  5871. transformation = transformation.multiply(axesbasis.getProjectionMatrix());
  5872. transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation));
  5873. transformation = transformation.multiply(axesbasis.getInverseProjectionMatrix());
  5874. var usAxesAligned = us.transform(transformation);
  5875. // Now we have done the transformation for aligning the axes.
  5876. // We still need to align the normals:
  5877. var normalsplane = CSG.Plane.fromNormalAndPoint(other.axisvector, new CSG.Vector3D(0, 0, 0));
  5878. var normalsbasis = new CSG.OrthoNormalBasis(normalsplane);
  5879. angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle();
  5880. angle2 = normalsbasis.to2D(other.normalvector).angle();
  5881. rotation = 180.0 * (angle2 - angle1) / Math.PI;
  5882. rotation += normalrotation;
  5883. transformation = transformation.multiply(normalsbasis.getProjectionMatrix());
  5884. transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation));
  5885. transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix());
  5886. // and translate to the destination point:
  5887. transformation = transformation.multiply(CSG.Matrix4x4.translation(other.point));
  5888. // var usAligned = us.transform(transformation);
  5889. return transformation;
  5890. },
  5891. axisLine: function() {
  5892. return new CSG.Line3D(this.point, this.axisvector);
  5893. },
  5894. // creates a new Connector, with the connection point moved in the direction of the axisvector
  5895. extend: function(distance) {
  5896. var newpoint = this.point.plus(this.axisvector.unit().times(distance));
  5897. return new CSG.Connector(newpoint, this.axisvector, this.normalvector);
  5898. }
  5899. };
  5900. CSG.ConnectorList = function(connectors) {
  5901. this.connectors_ = connectors ? connectors.slice() : [];
  5902. };
  5903. CSG.ConnectorList.defaultNormal = [0, 0, 1];
  5904. CSG.ConnectorList.fromPath2D = function(path2D, arg1, arg2) {
  5905. if (arguments.length === 3) {
  5906. return CSG.ConnectorList._fromPath2DTangents(path2D, arg1, arg2);
  5907. } else if (arguments.length == 2) {
  5908. return CSG.ConnectorList._fromPath2DExplicit(path2D, arg1);
  5909. } else {
  5910. throw("call with path2D and either 2 direction vectors, or a function returning direction vectors");
  5911. }
  5912. };
  5913. /*
  5914. * calculate the connector axisvectors by calculating the "tangent" for path2D.
  5915. * This is undefined for start and end points, so axis for these have to be manually
  5916. * provided.
  5917. */
  5918. CSG.ConnectorList._fromPath2DTangents = function(path2D, start, end) {
  5919. // path2D
  5920. var axis;
  5921. var pathLen = path2D.points.length;
  5922. var result = new CSG.ConnectorList([new CSG.Connector(path2D.points[0],
  5923. start, CSG.ConnectorList.defaultNormal)]);
  5924. // middle points
  5925. path2D.points.slice(1, pathLen - 1).forEach(function(p2, i) {
  5926. axis = path2D.points[i + 2].minus(path2D.points[i]).toVector3D(0);
  5927. result.appendConnector(new CSG.Connector(p2.toVector3D(0), axis,
  5928. CSG.ConnectorList.defaultNormal));
  5929. }, this);
  5930. result.appendConnector(new CSG.Connector(path2D.points[pathLen - 1], end,
  5931. CSG.ConnectorList.defaultNormal));
  5932. result.closed = path2D.closed;
  5933. return result;
  5934. };
  5935. /*
  5936. * angleIsh: either a static angle, or a function(point) returning an angle
  5937. */
  5938. CSG.ConnectorList._fromPath2DExplicit = function(path2D, angleIsh) {
  5939. function getAngle(angleIsh, pt, i) {
  5940. if (typeof angleIsh == 'function') {
  5941. angleIsh = angleIsh(pt, i);
  5942. }
  5943. return angleIsh;
  5944. }
  5945. var result = new CSG.ConnectorList(
  5946. path2D.points.map(function(p2, i) {
  5947. return new CSG.Connector(p2.toVector3D(0),
  5948. CSG.Vector3D.Create(1, 0, 0).rZ(getAngle(angleIsh, p2, i)),
  5949. CSG.ConnectorList.defaultNormal);
  5950. }, this)
  5951. );
  5952. result.closed = path2D.closed;
  5953. return result;
  5954. };
  5955. CSG.ConnectorList.prototype = {
  5956. setClosed: function(bool) {
  5957. this.closed = !!closed;
  5958. },
  5959. appendConnector: function(conn) {
  5960. this.connectors_.push(conn);
  5961. },
  5962. /*
  5963. * arguments: cagish: a cag or a function(connector) returning a cag
  5964. * closed: whether the 3d path defined by connectors location
  5965. * should be closed or stay open
  5966. * Note: don't duplicate connectors in the path
  5967. * TODO: consider an option "maySelfIntersect" to close & force union all single segments
  5968. */
  5969. followWith: function(cagish) {
  5970. this.verify();
  5971. function getCag(cagish, connector) {
  5972. if (typeof cagish == "function") {
  5973. cagish = cagish(connector.point, connector.axisvector, connector.normalvector);
  5974. }
  5975. return cagish;
  5976. }
  5977. var polygons = [], currCag;
  5978. var prevConnector = this.connectors_[this.connectors_.length - 1];
  5979. var prevCag = getCag(cagish, prevConnector);
  5980. // add walls
  5981. this.connectors_.forEach(function(connector, notFirst) {
  5982. currCag = getCag(cagish, connector);
  5983. if (notFirst || this.closed) {
  5984. polygons.push.apply(polygons, prevCag._toWallPolygons({
  5985. toConnector1: prevConnector, toConnector2: connector, cag: currCag}));
  5986. } else {
  5987. // it is the first, and shape not closed -> build start wall
  5988. polygons.push.apply(polygons,
  5989. currCag._toPlanePolygons({toConnector: connector, flipped: true}));
  5990. }
  5991. if (notFirst == this.connectors_.length - 1 && !this.closed) {
  5992. // build end wall
  5993. polygons.push.apply(polygons,
  5994. currCag._toPlanePolygons({toConnector: connector}));
  5995. }
  5996. prevCag = currCag;
  5997. prevConnector = connector;
  5998. }, this);
  5999. return CSG.fromPolygons(polygons).reTesselated().canonicalized();
  6000. },
  6001. /*
  6002. * general idea behind these checks: connectors need to have smooth transition from one to another
  6003. * TODO: add a check that 2 follow-on CAGs are not intersecting
  6004. */
  6005. verify: function() {
  6006. var connI, connI1, dPosToAxis, axisToNextAxis;
  6007. for (var i = 0; i < this.connectors_.length - 1; i++) {
  6008. connI = this.connectors_[i], connI1 = this.connectors_[i + 1];
  6009. if (connI1.point.minus(connI.point).dot(connI.axisvector) <= 0) {
  6010. throw("Invalid ConnectorList. Each connectors position needs to be within a <90deg range of previous connectors axisvector");
  6011. }
  6012. if (connI.axisvector.dot(connI1.axisvector) <= 0) {
  6013. throw("invalid ConnectorList. No neighboring connectors axisvectors may span a >=90deg angle");
  6014. }
  6015. }
  6016. }
  6017. };
  6018. //////////////////////////////////////
  6019. // # Class Path2D
  6020. CSG.Path2D = function(points, closed) {
  6021. closed = !!closed;
  6022. points = points || [];
  6023. // re-parse the points into CSG.Vector2D
  6024. // and remove any duplicate points
  6025. var prevpoint = null;
  6026. if (closed && (points.length > 0)) {
  6027. prevpoint = new CSG.Vector2D(points[points.length - 1]);
  6028. }
  6029. var newpoints = [];
  6030. points.map(function(point) {
  6031. point = new CSG.Vector2D(point);
  6032. var skip = false;
  6033. if (prevpoint !== null) {
  6034. var distance = point.distanceTo(prevpoint);
  6035. skip = distance < 1e-5;
  6036. }
  6037. if (!skip) newpoints.push(point);
  6038. prevpoint = point;
  6039. });
  6040. this.points = newpoints;
  6041. this.closed = closed;
  6042. };
  6043. /*
  6044. Construct a (part of a) circle. Parameters:
  6045. options.center: the center point of the arc (CSG.Vector2D or array [x,y])
  6046. options.radius: the circle radius (float)
  6047. options.startangle: the starting angle of the arc, in degrees
  6048. 0 degrees corresponds to [1,0]
  6049. 90 degrees to [0,1]
  6050. and so on
  6051. options.endangle: the ending angle of the arc, in degrees
  6052. options.resolution: number of points per 360 degree of rotation
  6053. options.maketangent: adds two extra tiny line segments at both ends of the circle
  6054. this ensures that the gradients at the edges are tangent to the circle
  6055. Returns a CSG.Path2D. The path is not closed (even if it is a 360 degree arc).
  6056. close() the resulting path if you want to create a true circle.
  6057. */
  6058. CSG.Path2D.arc = function(options) {
  6059. var center = CSG.parseOptionAs2DVector(options, "center", 0);
  6060. var radius = CSG.parseOptionAsFloat(options, "radius", 1);
  6061. var startangle = CSG.parseOptionAsFloat(options, "startangle", 0);
  6062. var endangle = CSG.parseOptionAsFloat(options, "endangle", 360);
  6063. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution2D);
  6064. var maketangent = CSG.parseOptionAsBool(options, "maketangent", false);
  6065. // no need to make multiple turns:
  6066. while (endangle - startangle >= 720) {
  6067. endangle -= 360;
  6068. }
  6069. while (endangle - startangle <= -720) {
  6070. endangle += 360;
  6071. }
  6072. var points = [],
  6073. point;
  6074. var absangledif = Math.abs(endangle - startangle);
  6075. if (absangledif < 1e-5) {
  6076. point = CSG.Vector2D.fromAngle(startangle / 180.0 * Math.PI).times(radius);
  6077. points.push(point.plus(center));
  6078. } else {
  6079. var numsteps = Math.floor(resolution * absangledif / 360) + 1;
  6080. var edgestepsize = numsteps * 0.5 / absangledif; // step size for half a degree
  6081. if (edgestepsize > 0.25) edgestepsize = 0.25;
  6082. var numsteps_mod = maketangent ? (numsteps + 2) : numsteps;
  6083. for (var i = 0; i <= numsteps_mod; i++) {
  6084. var step = i;
  6085. if (maketangent) {
  6086. step = (i - 1) * (numsteps - 2 * edgestepsize) / numsteps + edgestepsize;
  6087. if (step < 0) step = 0;
  6088. if (step > numsteps) step = numsteps;
  6089. }
  6090. var angle = startangle + step * (endangle - startangle) / numsteps;
  6091. point = CSG.Vector2D.fromAngle(angle / 180.0 * Math.PI).times(radius);
  6092. points.push(point.plus(center));
  6093. }
  6094. }
  6095. return new CSG.Path2D(points, false);
  6096. };
  6097. CSG.Path2D.prototype = {
  6098. concat: function(otherpath) {
  6099. if (this.closed || otherpath.closed) {
  6100. throw new Error("Paths must not be closed");
  6101. }
  6102. var newpoints = this.points.concat(otherpath.points);
  6103. return new CSG.Path2D(newpoints);
  6104. },
  6105. appendPoint: function(point) {
  6106. if (this.closed) {
  6107. throw new Error("Path must not be closed");
  6108. }
  6109. point = new CSG.Vector2D(point); // cast to Vector2D
  6110. var newpoints = this.points.concat([point]);
  6111. return new CSG.Path2D(newpoints);
  6112. },
  6113. appendPoints: function(points) {
  6114. if (this.closed) {
  6115. throw new Error("Path must not be closed");
  6116. }
  6117. var newpoints = this.points;
  6118. points.forEach(function(point) {
  6119. newpoints.push(new CSG.Vector2D(point)); // cast to Vector2D
  6120. })
  6121. return new CSG.Path2D(newpoints);
  6122. },
  6123. close: function() {
  6124. return new CSG.Path2D(this.points, true);
  6125. },
  6126. // Extrude the path by following it with a rectangle (upright, perpendicular to the path direction)
  6127. // Returns a CSG solid
  6128. // width: width of the extrusion, in the z=0 plane
  6129. // height: height of the extrusion in the z direction
  6130. // resolution: number of segments per 360 degrees for the curve in a corner
  6131. rectangularExtrude: function(width, height, resolution) {
  6132. var cag = this.expandToCAG(width / 2, resolution);
  6133. var result = cag.extrude({
  6134. offset: [0, 0, height]
  6135. });
  6136. return result;
  6137. },
  6138. // Expand the path to a CAG
  6139. // This traces the path with a circle with radius pathradius
  6140. expandToCAG: function(pathradius, resolution) {
  6141. var sides = [];
  6142. var numpoints = this.points.length;
  6143. var startindex = 0;
  6144. if (this.closed && (numpoints > 2)) startindex = -1;
  6145. var prevvertex;
  6146. for (var i = startindex; i < numpoints; i++) {
  6147. var pointindex = i;
  6148. if (pointindex < 0) pointindex = numpoints - 1;
  6149. var point = this.points[pointindex];
  6150. var vertex = new CAG.Vertex(point);
  6151. if (i > startindex) {
  6152. var side = new CAG.Side(prevvertex, vertex);
  6153. sides.push(side);
  6154. }
  6155. prevvertex = vertex;
  6156. }
  6157. var shellcag = CAG.fromSides(sides);
  6158. var expanded = shellcag.expandedShell(pathradius, resolution);
  6159. return expanded;
  6160. },
  6161. innerToCAG: function() {
  6162. if (!this.closed) throw new Error("The path should be closed!");
  6163. return CAG.fromPoints(this.points);
  6164. },
  6165. transform: function(matrix4x4) {
  6166. var newpoints = this.points.map(function(point) {
  6167. return point.multiply4x4(matrix4x4);
  6168. });
  6169. return new CSG.Path2D(newpoints, this.closed);
  6170. },
  6171. appendBezier: function(controlpoints, options) {
  6172. if (arguments.length < 2) {
  6173. options = {};
  6174. }
  6175. if (this.closed) {
  6176. throw new Error("Path must not be closed");
  6177. }
  6178. if (!(controlpoints instanceof Array)) {
  6179. throw new Error("appendBezier: should pass an array of control points")
  6180. }
  6181. if (controlpoints.length < 1) {
  6182. throw new Error("appendBezier: need at least 1 control point")
  6183. }
  6184. if (this.points.length < 1) {
  6185. throw new Error("appendBezier: path must already contain a point (the endpoint of the path is used as the starting point for the bezier curve)");
  6186. }
  6187. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution2D);
  6188. if (resolution < 4) resolution = 4;
  6189. var factorials = [];
  6190. var controlpoints_parsed = [];
  6191. controlpoints_parsed.push(this.points[this.points.length - 1]); // start at the previous end point
  6192. for (var i = 0; i < controlpoints.length; ++i) {
  6193. var p = controlpoints[i];
  6194. if (p === null) {
  6195. // we can pass null as the first control point. In that case a smooth gradient is ensured:
  6196. if (i != 0) {
  6197. throw new Error("appendBezier: null can only be passed as the first control point");
  6198. }
  6199. if (controlpoints.length < 2) {
  6200. throw new Error("appendBezier: null can only be passed if there is at least one more control point");
  6201. }
  6202. var lastBezierControlPoint;
  6203. if ('lastBezierControlPoint' in this) {
  6204. lastBezierControlPoint = this.lastBezierControlPoint;
  6205. } else {
  6206. if (this.points.length < 2) {
  6207. throw new Error("appendBezier: null is passed as a control point but this requires a previous bezier curve or at least two points in the existing path");
  6208. }
  6209. lastBezierControlPoint = this.points[this.points.length - 2];
  6210. }
  6211. // mirror the last bezier control point:
  6212. p = this.points[this.points.length - 1].times(2).minus(lastBezierControlPoint);
  6213. } else {
  6214. p = new CSG.Vector2D(p); // cast to Vector2D
  6215. }
  6216. controlpoints_parsed.push(p);
  6217. }
  6218. var bezier_order = controlpoints_parsed.length - 1;
  6219. var fact = 1;
  6220. for (var i = 0; i <= bezier_order; ++i) {
  6221. if (i > 0) fact *= i;
  6222. factorials.push(fact);
  6223. }
  6224. var binomials = [];
  6225. for (var i = 0; i <= bezier_order; ++i) {
  6226. var binomial = factorials[bezier_order] / (factorials[i] * factorials[bezier_order - i]);
  6227. binomials.push(binomial);
  6228. }
  6229. var getPointForT = function(t) {
  6230. var t_k = 1; // = pow(t,k)
  6231. var one_minus_t_n_minus_k = Math.pow(1 - t, bezier_order); // = pow( 1-t, bezier_order - k)
  6232. var inv_1_minus_t = (t != 1) ? (1 / (1 - t)) : 1;
  6233. var point = new CSG.Vector2D(0, 0);
  6234. for (var k = 0; k <= bezier_order; ++k) {
  6235. if (k == bezier_order) one_minus_t_n_minus_k = 1;
  6236. var bernstein_coefficient = binomials[k] * t_k * one_minus_t_n_minus_k;
  6237. point = point.plus(controlpoints_parsed[k].times(bernstein_coefficient));
  6238. t_k *= t;
  6239. one_minus_t_n_minus_k *= inv_1_minus_t;
  6240. }
  6241. return point;
  6242. };
  6243. var newpoints = [];
  6244. var newpoints_t = [];
  6245. var numsteps = bezier_order + 1;
  6246. for (var i = 0; i < numsteps; ++i) {
  6247. var t = i / (numsteps - 1);
  6248. var point = getPointForT(t);
  6249. newpoints.push(point);
  6250. newpoints_t.push(t);
  6251. }
  6252. // subdivide each segment until the angle at each vertex becomes small enough:
  6253. var subdivide_base = 1;
  6254. var maxangle = Math.PI * 2 / resolution; // segments may have differ no more in angle than this
  6255. var maxsinangle = Math.sin(maxangle);
  6256. while (subdivide_base < newpoints.length - 1) {
  6257. var dir1 = newpoints[subdivide_base].minus(newpoints[subdivide_base - 1]).unit();
  6258. var dir2 = newpoints[subdivide_base + 1].minus(newpoints[subdivide_base]).unit();
  6259. var sinangle = dir1.cross(dir2); // this is the sine of the angle
  6260. if (Math.abs(sinangle) > maxsinangle) {
  6261. // angle is too big, we need to subdivide
  6262. var t0 = newpoints_t[subdivide_base - 1];
  6263. var t1 = newpoints_t[subdivide_base + 1];
  6264. var t0_new = t0 + (t1 - t0) * 1 / 3;
  6265. var t1_new = t0 + (t1 - t0) * 2 / 3;
  6266. var point0_new = getPointForT(t0_new);
  6267. var point1_new = getPointForT(t1_new);
  6268. // remove the point at subdivide_base and replace with 2 new points:
  6269. newpoints.splice(subdivide_base, 1, point0_new, point1_new);
  6270. newpoints_t.splice(subdivide_base, 1, t0_new, t1_new);
  6271. // re - evaluate the angles, starting at the previous junction since it has changed:
  6272. subdivide_base--;
  6273. if (subdivide_base < 1) subdivide_base = 1;
  6274. } else {
  6275. ++subdivide_base;
  6276. }
  6277. }
  6278. // append to the previous points, but skip the first new point because it is identical to the last point:
  6279. newpoints = this.points.concat(newpoints.slice(1));
  6280. var result = new CSG.Path2D(newpoints);
  6281. result.lastBezierControlPoint = controlpoints_parsed[controlpoints_parsed.length - 2];
  6282. return result;
  6283. },
  6284. /*
  6285. options:
  6286. .resolution // smoothness of the arc (number of segments per 360 degree of rotation)
  6287. // to create a circular arc:
  6288. .radius
  6289. // to create an elliptical arc:
  6290. .xradius
  6291. .yradius
  6292. .xaxisrotation // the rotation (in degrees) of the x axis of the ellipse with respect to the x axis of our coordinate system
  6293. // this still leaves 4 possible arcs between the two given points. The following two flags select which one we draw:
  6294. .clockwise // = true | false (default is false). Two of the 4 solutions draw clockwise with respect to the center point, the other 2 counterclockwise
  6295. .large // = true | false (default is false). Two of the 4 solutions are an arc longer than 180 degrees, the other two are <= 180 degrees
  6296. This implementation follows the SVG arc specs. For the details see
  6297. http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
  6298. */
  6299. appendArc: function(endpoint, options) {
  6300. var decimals = 100000;
  6301. if (arguments.length < 2) {
  6302. options = {};
  6303. }
  6304. if (this.closed) {
  6305. throw new Error("Path must not be closed");
  6306. }
  6307. if (this.points.length < 1) {
  6308. throw new Error("appendArc: path must already contain a point (the endpoint of the path is used as the starting point for the arc)");
  6309. }
  6310. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution2D);
  6311. if (resolution < 4) resolution = 4;
  6312. var xradius, yradius;
  6313. if (('xradius' in options) || ('yradius' in options)) {
  6314. if ('radius' in options) {
  6315. throw new Error("Should either give an xradius and yradius parameter, or a radius parameter");
  6316. }
  6317. xradius = CSG.parseOptionAsFloat(options, "xradius", 0);
  6318. yradius = CSG.parseOptionAsFloat(options, "yradius", 0);
  6319. } else {
  6320. xradius = CSG.parseOptionAsFloat(options, "radius", 0);
  6321. yradius = xradius;
  6322. }
  6323. var xaxisrotation = CSG.parseOptionAsFloat(options, "xaxisrotation", 0);
  6324. var clockwise = CSG.parseOptionAsBool(options, "clockwise", false);
  6325. var largearc = CSG.parseOptionAsBool(options, "large", false);
  6326. var startpoint = this.points[this.points.length - 1];
  6327. endpoint = new CSG.Vector2D(endpoint);
  6328. // round to precision in order to have determinate calculations
  6329. xradius = Math.round(xradius*decimals)/decimals;
  6330. yradius = Math.round(yradius*decimals)/decimals;
  6331. endpoint = new CSG.Vector2D(Math.round(endpoint.x*decimals)/decimals,Math.round(endpoint.y*decimals)/decimals);
  6332. var sweep_flag = !clockwise;
  6333. var newpoints = [];
  6334. if ((xradius == 0) || (yradius == 0)) {
  6335. // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes:
  6336. // If rx = 0 or ry = 0, then treat this as a straight line from (x1, y1) to (x2, y2) and stop
  6337. newpoints.push(endpoint);
  6338. } else {
  6339. xradius = Math.abs(xradius);
  6340. yradius = Math.abs(yradius);
  6341. // see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes :
  6342. var phi = xaxisrotation * Math.PI / 180.0;
  6343. var cosphi = Math.cos(phi);
  6344. var sinphi = Math.sin(phi);
  6345. var minushalfdistance = startpoint.minus(endpoint).times(0.5);
  6346. // F.6.5.1:
  6347. // round to precision in order to have determinate calculations
  6348. var x = Math.round((cosphi * minushalfdistance.x + sinphi * minushalfdistance.y)*decimals)/decimals;
  6349. var y = Math.round((-sinphi * minushalfdistance.x + cosphi * minushalfdistance.y)*decimals)/decimals;
  6350. var start_trd = new CSG.Vector2D(x,y);
  6351. // F.6.6.2:
  6352. var biglambda = (start_trd.x * start_trd.x) / (xradius * xradius) + (start_trd.y * start_trd.y) / (yradius * yradius);
  6353. if (biglambda > 1.0) {
  6354. // F.6.6.3:
  6355. var sqrtbiglambda = Math.sqrt(biglambda);
  6356. xradius *= sqrtbiglambda;
  6357. yradius *= sqrtbiglambda;
  6358. // round to precision in order to have determinate calculations
  6359. xradius = Math.round(xradius*decimals)/decimals;
  6360. yradius = Math.round(yradius*decimals)/decimals;
  6361. }
  6362. // F.6.5.2:
  6363. var multiplier1 = Math.sqrt((xradius * xradius * yradius * yradius - xradius * xradius * start_trd.y * start_trd.y - yradius * yradius * start_trd.x * start_trd.x) / (xradius * xradius * start_trd.y * start_trd.y + yradius * yradius * start_trd.x * start_trd.x));
  6364. if (sweep_flag == largearc) multiplier1 = -multiplier1;
  6365. var center_trd = new CSG.Vector2D(xradius * start_trd.y / yradius, -yradius * start_trd.x / xradius).times(multiplier1);
  6366. // F.6.5.3:
  6367. var center = new CSG.Vector2D(cosphi * center_trd.x - sinphi * center_trd.y, sinphi * center_trd.x + cosphi * center_trd.y).plus((startpoint.plus(endpoint)).times(0.5));
  6368. // F.6.5.5:
  6369. var vec1 = new CSG.Vector2D((start_trd.x - center_trd.x) / xradius, (start_trd.y - center_trd.y) / yradius);
  6370. var vec2 = new CSG.Vector2D((-start_trd.x - center_trd.x) / xradius, (-start_trd.y - center_trd.y) / yradius);
  6371. var theta1 = vec1.angleRadians();
  6372. var theta2 = vec2.angleRadians();
  6373. var deltatheta = theta2 - theta1;
  6374. deltatheta = deltatheta % (2 * Math.PI);
  6375. if ((!sweep_flag) && (deltatheta > 0)) {
  6376. deltatheta -= 2 * Math.PI;
  6377. } else if ((sweep_flag) && (deltatheta < 0)) {
  6378. deltatheta += 2 * Math.PI;
  6379. }
  6380. // Ok, we have the center point and angle range (from theta1, deltatheta radians) so we can create the ellipse
  6381. var numsteps = Math.ceil(Math.abs(deltatheta) / (2 * Math.PI) * resolution) + 1;
  6382. if (numsteps < 1) numsteps = 1;
  6383. for (var step = 1; step <= numsteps; step++) {
  6384. var theta = theta1 + step / numsteps * deltatheta;
  6385. var costheta = Math.cos(theta);
  6386. var sintheta = Math.sin(theta);
  6387. // F.6.3.1:
  6388. var point = new CSG.Vector2D(cosphi * xradius * costheta - sinphi * yradius * sintheta, sinphi * xradius * costheta + cosphi * yradius * sintheta).plus(center);
  6389. newpoints.push(point);
  6390. }
  6391. }
  6392. newpoints = this.points.concat(newpoints);
  6393. var result = new CSG.Path2D(newpoints);
  6394. return result;
  6395. },
  6396. };
  6397. // Add several convenience methods to the classes that support a transform() method:
  6398. CSG.addTransformationMethodsToPrototype = function(prot) {
  6399. prot.mirrored = function(plane) {
  6400. return this.transform(CSG.Matrix4x4.mirroring(plane));
  6401. };
  6402. prot.mirroredX = function() {
  6403. var plane = new CSG.Plane(CSG.Vector3D.Create(1, 0, 0), 0);
  6404. return this.mirrored(plane);
  6405. };
  6406. prot.mirroredY = function() {
  6407. var plane = new CSG.Plane(CSG.Vector3D.Create(0, 1, 0), 0);
  6408. return this.mirrored(plane);
  6409. };
  6410. prot.mirroredZ = function() {
  6411. var plane = new CSG.Plane(CSG.Vector3D.Create(0, 0, 1), 0);
  6412. return this.mirrored(plane);
  6413. };
  6414. prot.tr = function(v) {
  6415. return this.transform(CSG.Matrix4x4.translation(v));
  6416. };
  6417. prot.scale = function(f) {
  6418. return this.transform(CSG.Matrix4x4.scaling(f));
  6419. };
  6420. prot.rX = function(deg) {
  6421. return this.transform(CSG.Matrix4x4.rotationX(deg));
  6422. };
  6423. prot.rY = function(deg) {
  6424. return this.transform(CSG.Matrix4x4.rotationY(deg));
  6425. };
  6426. prot.rZ = function(deg) {
  6427. return this.transform(CSG.Matrix4x4.rotationZ(deg));
  6428. };
  6429. prot.rotate = function(rotationCenter, rotationAxis, degrees) {
  6430. return this.transform(CSG.Matrix4x4.rotation(rotationCenter, rotationAxis, degrees));
  6431. };
  6432. prot.rotateEulerAngles = function(alpha, beta, gamma, position) {
  6433. position = position || [0,0,0];
  6434. var Rz1 = CSG.Matrix4x4.rotationZ(alpha);
  6435. var Rx = CSG.Matrix4x4.rotationX(beta);
  6436. var Rz2 = CSG.Matrix4x4.rotationZ(gamma);
  6437. var T = CSG.Matrix4x4.translation(new CSG.Vector3D(position));
  6438. return this.transform(Rz2.multiply(Rx).multiply(Rz1).multiply(T));
  6439. };
  6440. };
  6441. // TODO: consider generalization and adding to addTransformationMethodsToPrototype
  6442. CSG.addCenteringToPrototype = function(prot, axes) {
  6443. prot.center = function(cAxes) {
  6444. cAxes = Array.prototype.map.call(arguments, function(a) {
  6445. return a; //.toLowerCase();
  6446. });
  6447. // no args: center on all axes
  6448. if (!cAxes.length) {
  6449. cAxes = axes.slice();
  6450. }
  6451. var b = this.getBounds();
  6452. return this.tr(axes.map(function(a) {
  6453. return cAxes.indexOf(a) > -1 ?
  6454. -(b[0][a] + b[1][a])/2 : 0;
  6455. }));
  6456. };
  6457. };
  6458. //////////////////
  6459. // CAG: solid area geometry: like CSG but 2D
  6460. // Each area consists of a number of sides
  6461. // Each side is a line between 2 points
  6462. var CAG = function() {
  6463. this.sides = [];
  6464. this.isCanonicalized = false;
  6465. };
  6466. // create from an untyped object with identical property names:
  6467. CAG.fromObject = function(obj) {
  6468. var sides = obj.sides.map(function(s) {
  6469. return CAG.Side.fromObject(s);
  6470. });
  6471. var cag = CAG.fromSides(sides);
  6472. return cag;
  6473. }
  6474. // Construct a CAG from a list of `CAG.Side` instances.
  6475. CAG.fromSides = function(sides) {
  6476. var cag = new CAG();
  6477. cag.sides = sides;
  6478. return cag;
  6479. };
  6480. // Construct a CAG from a list of points (a polygon)
  6481. // Rotation direction of the points is not relevant. Points can be a convex or concave polygon.
  6482. // Polygon must not self intersect
  6483. CAG.fromPoints = function(points) {
  6484. var numpoints = points.length;
  6485. if (numpoints < 3) throw new Error("CAG shape needs at least 3 points");
  6486. var sides = [];
  6487. var prevpoint = new CSG.Vector2D(points[numpoints - 1]);
  6488. var prevvertex = new CAG.Vertex(prevpoint);
  6489. points.map(function(p) {
  6490. var point = new CSG.Vector2D(p);
  6491. var vertex = new CAG.Vertex(point);
  6492. var side = new CAG.Side(prevvertex, vertex);
  6493. sides.push(side);
  6494. prevvertex = vertex;
  6495. });
  6496. var result = CAG.fromSides(sides);
  6497. if (result.isSelfIntersecting()) {
  6498. throw new Error("Polygon is self intersecting!");
  6499. }
  6500. var area = result.area();
  6501. if (Math.abs(area) < 1e-5) {
  6502. throw new Error("Degenerate polygon!");
  6503. }
  6504. if (area < 0) {
  6505. result = result.flipped();
  6506. }
  6507. result = result.canonicalized();
  6508. return result;
  6509. };
  6510. // Like CAG.fromPoints but does not check if it's a valid polygon.
  6511. // Points should rotate counter clockwise
  6512. CAG.fromPointsNoCheck = function(points) {
  6513. var sides = [];
  6514. var prevpoint = new CSG.Vector2D(points[points.length - 1]);
  6515. var prevvertex = new CAG.Vertex(prevpoint);
  6516. points.map(function(p) {
  6517. var point = new CSG.Vector2D(p);
  6518. var vertex = new CAG.Vertex(point);
  6519. var side = new CAG.Side(prevvertex, vertex);
  6520. sides.push(side);
  6521. prevvertex = vertex;
  6522. });
  6523. return CAG.fromSides(sides);
  6524. };
  6525. // Converts a CSG to a CAG. The CSG must consist of polygons with only z coordinates +1 and -1
  6526. // as constructed by CAG._toCSGWall(-1, 1). This is so we can use the 3D union(), intersect() etc
  6527. CAG.fromFakeCSG = function(csg) {
  6528. var sides = csg.polygons.map(function(p) {
  6529. return CAG.Side._fromFakePolygon(p);
  6530. })
  6531. .filter(function(s) {
  6532. return s !== null;
  6533. });
  6534. return CAG.fromSides(sides);
  6535. };
  6536. // see if the line between p0start and p0end intersects with the line between p1start and p1end
  6537. // returns true if the lines strictly intersect, the end points are not counted!
  6538. CAG.linesIntersect = function(p0start, p0end, p1start, p1end) {
  6539. if (p0end.equals(p1start) || p1end.equals(p0start)) {
  6540. var d = p1end.minus(p1start).unit().plus(p0end.minus(p0start).unit()).length();
  6541. if (d < 1e-5) {
  6542. return true;
  6543. }
  6544. } else {
  6545. var d0 = p0end.minus(p0start);
  6546. var d1 = p1end.minus(p1start);
  6547. if (Math.abs(d0.cross(d1)) < 1e-9) return false; // lines are parallel
  6548. var alphas = CSG.solve2Linear(-d0.x, d1.x, -d0.y, d1.y, p0start.x - p1start.x, p0start.y - p1start.y);
  6549. if ((alphas[0] > 1e-6) && (alphas[0] < 0.999999) && (alphas[1] > 1e-5) && (alphas[1] < 0.999999)) return true;
  6550. // if( (alphas[0] >= 0) && (alphas[0] <= 1) && (alphas[1] >= 0) && (alphas[1] <= 1) ) return true;
  6551. }
  6552. return false;
  6553. };
  6554. /* Construct a circle
  6555. options:
  6556. center: a 2D center point
  6557. radius: a scalar
  6558. resolution: number of sides per 360 degree rotation
  6559. returns a CAG object
  6560. */
  6561. CAG.circle = function(options) {
  6562. options = options || {};
  6563. var center = CSG.parseOptionAs2DVector(options, "center", [0, 0]);
  6564. var radius = CSG.parseOptionAsFloat(options, "radius", 1);
  6565. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution2D);
  6566. var sides = [];
  6567. var prevvertex;
  6568. // JY - throw out circles with a radius too small (negative)
  6569. if(radius < 0) {
  6570. throw new Error("Radius should be positive.");
  6571. }
  6572. else if(radius < 0.0005) {
  6573. return(new CAG);
  6574. }
  6575. for (var i = 0; i <= resolution; i++) {
  6576. var radians = 2 * Math.PI * i / resolution;
  6577. var point = CSG.Vector2D.fromAngleRadians(radians).times(radius).plus(center);
  6578. var vertex = new CAG.Vertex(point);
  6579. if (i > 0) {
  6580. sides.push(new CAG.Side(prevvertex, vertex));
  6581. }
  6582. prevvertex = vertex;
  6583. }
  6584. return CAG.fromSides(sides);
  6585. };
  6586. /* Construct an ellispe
  6587. options:
  6588. center: a 2D center point
  6589. radius: a 2D vector with width and height
  6590. resolution: number of sides per 360 degree rotation
  6591. returns a CAG object
  6592. */
  6593. CAG.ellipse = function(options) {
  6594. options = options || {};
  6595. var c = CSG.parseOptionAs2DVector(options, "center", [0, 0]);
  6596. var r = CSG.parseOptionAs2DVector(options, "radius", [1, 1]);
  6597. r = r.abs(); // negative radii make no sense
  6598. var res = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution2D);
  6599. var e2 = new CSG.Path2D([[c.x,c.y + r.y]]);
  6600. e2 = e2.appendArc([c.x,c.y - r.y], {
  6601. xradius: r.x,
  6602. yradius: r.y,
  6603. xaxisrotation: 0,
  6604. resolution: res,
  6605. clockwise: true,
  6606. large: false,
  6607. });
  6608. e2 = e2.appendArc([c.x,c.y + r.y], {
  6609. xradius: r.x,
  6610. yradius: r.y,
  6611. xaxisrotation: 0,
  6612. resolution: res,
  6613. clockwise: true,
  6614. large: false,
  6615. });
  6616. e2 = e2.close();
  6617. return e2.innerToCAG();
  6618. };
  6619. /* Construct a rectangle
  6620. options:
  6621. center: a 2D center point
  6622. radius: a 2D vector with width and height
  6623. returns a CAG object
  6624. */
  6625. CAG.rectangle = function(options) {
  6626. options = options || {};
  6627. var c, r;
  6628. if (('corner1' in options) || ('corner2' in options)) {
  6629. if (('center' in options) || ('radius' in options)) {
  6630. throw new Error("rectangle: should either give a radius and center parameter, or a corner1 and corner2 parameter")
  6631. }
  6632. corner1 = CSG.parseOptionAs2DVector(options, "corner1", [0, 0]);
  6633. corner2 = CSG.parseOptionAs2DVector(options, "corner2", [1, 1]);
  6634. c = corner1.plus(corner2).times(0.5);
  6635. r = corner2.minus(corner1).times(0.5);
  6636. } else {
  6637. c = CSG.parseOptionAs2DVector(options, "center", [0, 0]);
  6638. r = CSG.parseOptionAs2DVector(options, "radius", [1, 1]);
  6639. }
  6640. //r = r.abs(); // negative radii make no sense
  6641. if(r.x < 0 || r.y < 0) {
  6642. throw new Error("Dimension should be positive.");
  6643. }
  6644. // throw out squares with either dimension too close to zero - JY
  6645. if (r.x < 0.0005 || r.y < 0.0005){
  6646. console.log("Throwing out a zero-length rectangle.");
  6647. return new CAG;
  6648. }
  6649. var rswap = new CSG.Vector2D(r.x, -r.y);
  6650. var points = [
  6651. c.plus(r), c.plus(rswap), c.minus(r), c.minus(rswap)
  6652. ];
  6653. return CAG.fromPoints(points);
  6654. };
  6655. // var r = CSG.roundedRectangle({
  6656. // center: [0, 0],
  6657. // radius: [2, 1],
  6658. // roundradius: 0.2,
  6659. // resolution: 8,
  6660. // });
  6661. CAG.roundedRectangle = function(options) {
  6662. options = options || {};
  6663. var center, radius;
  6664. if (('corner1' in options) || ('corner2' in options)) {
  6665. if (('center' in options) || ('radius' in options)) {
  6666. throw new Error("roundedRectangle: should either give a radius and center parameter, or a corner1 and corner2 parameter")
  6667. }
  6668. corner1 = CSG.parseOptionAs2DVector(options, "corner1", [0, 0]);
  6669. corner2 = CSG.parseOptionAs2DVector(options, "corner2", [1, 1]);
  6670. center = corner1.plus(corner2).times(0.5);
  6671. radius = corner2.minus(corner1).times(0.5);
  6672. } else {
  6673. center = CSG.parseOptionAs2DVector(options, "center", [0, 0]);
  6674. radius = CSG.parseOptionAs2DVector(options, "radius", [1, 1]);
  6675. }
  6676. radius = radius.abs(); // negative radii make no sense
  6677. var roundradius = CSG.parseOptionAsFloat(options, "roundradius", 0.2);
  6678. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution2D);
  6679. var maxroundradius = Math.min(radius.x, radius.y);
  6680. maxroundradius -= 0.1;
  6681. roundradius = Math.min(roundradius, maxroundradius);
  6682. roundradius = Math.max(0, roundradius);
  6683. radius = new CSG.Vector2D(radius.x - roundradius, radius.y - roundradius);
  6684. var rect = CAG.rectangle({
  6685. center: center,
  6686. radius: radius
  6687. });
  6688. if (roundradius > 0) {
  6689. rect = rect.expand(roundradius, resolution);
  6690. }
  6691. return rect;
  6692. };
  6693. // Reconstruct a CAG from the output of toCompactBinary()
  6694. CAG.fromCompactBinary = function(bin) {
  6695. if (bin['class'] != "CAG") throw new Error("Not a CAG");
  6696. var vertices = [];
  6697. var vertexData = bin.vertexData;
  6698. var numvertices = vertexData.length / 2;
  6699. var arrayindex = 0;
  6700. for (var vertexindex = 0; vertexindex < numvertices; vertexindex++) {
  6701. var x = vertexData[arrayindex++];
  6702. var y = vertexData[arrayindex++];
  6703. var pos = new CSG.Vector2D(x, y);
  6704. var vertex = new CAG.Vertex(pos);
  6705. vertices.push(vertex);
  6706. }
  6707. var sides = [];
  6708. var numsides = bin.sideVertexIndices.length / 2;
  6709. arrayindex = 0;
  6710. for (var sideindex = 0; sideindex < numsides; sideindex++) {
  6711. var vertexindex0 = bin.sideVertexIndices[arrayindex++];
  6712. var vertexindex1 = bin.sideVertexIndices[arrayindex++];
  6713. var side = new CAG.Side(vertices[vertexindex0], vertices[vertexindex1]);
  6714. sides.push(side);
  6715. }
  6716. var cag = CAG.fromSides(sides);
  6717. cag.isCanonicalized = true;
  6718. return cag;
  6719. };
  6720. function fnSortByIndex(a, b) {
  6721. return a.index - b.index;
  6722. }
  6723. CAG.prototype = {
  6724. toString: function() {
  6725. var result = "CAG (" + this.sides.length + " sides):\n";
  6726. this.sides.map(function(side) {
  6727. result += " " + side.toString() + "\n";
  6728. });
  6729. return result;
  6730. },
  6731. _toCSGWall: function(z0, z1) {
  6732. var polygons = this.sides.map(function(side) {
  6733. return side.toPolygon3D(z0, z1);
  6734. });
  6735. return CSG.fromPolygons(polygons);
  6736. },
  6737. _toVector3DPairs: function(m) {
  6738. // transform m
  6739. var pairs = this.sides.map(function(side) {
  6740. var p0 = side.vertex0.pos, p1 = side.vertex1.pos;
  6741. return [CSG.Vector3D.Create(p0.x, p0.y, 0),
  6742. CSG.Vector3D.Create(p1.x, p1.y, 0)];
  6743. });
  6744. if (typeof m != 'undefined') {
  6745. pairs = pairs.map(function(pair) {
  6746. return pair.map(function(v) {
  6747. return v.transform(m);
  6748. });
  6749. });
  6750. }
  6751. return pairs;
  6752. },
  6753. /*
  6754. * transform a cag into the polygons of a corresponding 3d plane, positioned per options
  6755. * Accepts a connector for plane positioning, or optionally
  6756. * single translation, axisVector, normalVector arguments
  6757. * (toConnector has precedence over single arguments if provided)
  6758. */
  6759. _toPlanePolygons: function(options) {
  6760. var flipped = options.flipped || false;
  6761. // reference connector for transformation
  6762. var origin = [0, 0, 0], defaultAxis = [0, 0, 1], defaultNormal = [0, 1, 0];
  6763. var thisConnector = new CSG.Connector(origin, defaultAxis, defaultNormal);
  6764. // translated connector per options
  6765. var translation = options.translation || origin;
  6766. var axisVector = options.axisVector || defaultAxis;
  6767. var normalVector = options.normalVector || defaultNormal;
  6768. // will override above if options has toConnector
  6769. var toConnector = options.toConnector ||
  6770. new CSG.Connector(translation, axisVector, normalVector);
  6771. // resulting transform
  6772. var m = thisConnector.getTransformationTo(toConnector, false, 0);
  6773. // create plane as a (partial non-closed) CSG in XY plane
  6774. var bounds = this.getBounds();
  6775. bounds[0] = bounds[0].minus(new CSG.Vector2D(1, 1));
  6776. bounds[1] = bounds[1].plus(new CSG.Vector2D(1, 1));
  6777. var csgshell = this._toCSGWall(-1, 1);
  6778. var csgplane = CSG.fromPolygons([new CSG.Polygon([
  6779. new CSG.Vertex(new CSG.Vector3D(bounds[0].x, bounds[0].y, 0)),
  6780. new CSG.Vertex(new CSG.Vector3D(bounds[1].x, bounds[0].y, 0)),
  6781. new CSG.Vertex(new CSG.Vector3D(bounds[1].x, bounds[1].y, 0)),
  6782. new CSG.Vertex(new CSG.Vector3D(bounds[0].x, bounds[1].y, 0))
  6783. ])]);
  6784. if (flipped) {
  6785. csgplane = csgplane.invert();
  6786. }
  6787. // intersectSub -> prevent premature retesselate/canonicalize
  6788. csgplane = csgplane.intersectSub(csgshell);
  6789. // only keep the polygons in the z plane:
  6790. var polys = csgplane.polygons.filter(function(polygon) {
  6791. return Math.abs(polygon.plane.normal.z) > 0.99;
  6792. });
  6793. // finally, position the plane per passed transformations
  6794. return polys.map(function(poly) {
  6795. return poly.transform(m);
  6796. });
  6797. },
  6798. /*
  6799. * given 2 connectors, this returns all polygons of a "wall" between 2
  6800. * copies of this cag, positioned in 3d space as "bottom" and
  6801. * "top" plane per connectors toConnector1, and toConnector2, respectively
  6802. */
  6803. _toWallPolygons: function(options) {
  6804. // normals are going to be correct as long as toConn2.point - toConn1.point
  6805. // points into cag normal direction (check in caller)
  6806. // arguments: options.toConnector1, options.toConnector2, options.cag
  6807. // walls go from toConnector1 to toConnector2
  6808. // optionally, target cag to point to - cag needs to have same number of sides as this!
  6809. var origin = [0, 0, 0], defaultAxis = [0, 0, 1], defaultNormal = [0, 1, 0];
  6810. var thisConnector = new CSG.Connector(origin, defaultAxis, defaultNormal);
  6811. // arguments:
  6812. var toConnector1 = options.toConnector1;
  6813. // var toConnector2 = new CSG.Connector([0, 0, -30], defaultAxis, defaultNormal);
  6814. var toConnector2 = options.toConnector2;
  6815. if (!(toConnector1 instanceof CSG.Connector && toConnector2 instanceof CSG.Connector)) {
  6816. throw('could not parse CSG.Connector arguments toConnector1 or toConnector2');
  6817. }
  6818. if (options.cag) {
  6819. if (options.cag.sides.length != this.sides.length) {
  6820. throw('target cag needs same sides count as start cag');
  6821. }
  6822. }
  6823. // target cag is same as this unless specified
  6824. var toCag = options.cag || this;
  6825. var m1 = thisConnector.getTransformationTo(toConnector1, false, 0);
  6826. var m2 = thisConnector.getTransformationTo(toConnector2, false, 0);
  6827. var vps1 = this._toVector3DPairs(m1);
  6828. var vps2 = toCag._toVector3DPairs(m2);
  6829. var polygons = [];
  6830. vps1.forEach(function(vp1, i) {
  6831. polygons.push(new CSG.Polygon([
  6832. new CSG.Vertex(vps2[i][1]), new CSG.Vertex(vps2[i][0]), new CSG.Vertex(vp1[0])]));
  6833. polygons.push(new CSG.Polygon([
  6834. new CSG.Vertex(vps2[i][1]), new CSG.Vertex(vp1[0]), new CSG.Vertex(vp1[1])]));
  6835. });
  6836. return polygons;
  6837. },
  6838. union: function(cag) {
  6839. var cags;
  6840. if (cag instanceof Array) {
  6841. cags = cag;
  6842. } else {
  6843. cags = [cag];
  6844. }
  6845. // TODO: test if this is still broken, and if it breaks with rotateExtrude instead.
  6846. /* // checking for mixed use broke rotate_extrude. I'm taking it out.
  6847. for(var i = 0; i < cags.length; i++) {
  6848. if (!(cags[i] instanceof CAG))
  6849. throw("ERROR: don't mix 2d and 3d shapes in union");
  6850. return new CAG();
  6851. }
  6852. */
  6853. var r = this._toCSGWall(-1, 1);
  6854. var r = r.union(
  6855. cags.map(function(cag) {
  6856. return cag._toCSGWall(-1, 1).reTesselated();
  6857. }), false, false)
  6858. return CAG.fromFakeCSG(r).canonicalized();
  6859. },
  6860. subtract: function(cag) {
  6861. var cags;
  6862. if (cag instanceof Array) {
  6863. cags = cag;
  6864. } else {
  6865. cags = [cag];
  6866. }
  6867. var r = this._toCSGWall(-1, 1);
  6868. cags.map(function(cag) {
  6869. r = r.subtractSub(cag._toCSGWall(-1, 1), false, false);
  6870. });
  6871. r = r.reTesselated();
  6872. r = r.canonicalized();
  6873. r = CAG.fromFakeCSG(r);
  6874. r = r.canonicalized();
  6875. return r;
  6876. },
  6877. intersect: function(cag) {
  6878. var cags;
  6879. if (cag instanceof Array) {
  6880. cags = cag;
  6881. } else {
  6882. cags = [cag];
  6883. }
  6884. var r = this._toCSGWall(-1, 1);
  6885. cags.map(function(cag) {
  6886. r = r.intersectSub(cag._toCSGWall(-1, 1), false, false);
  6887. });
  6888. r = r.reTesselated();
  6889. r = r.canonicalized();
  6890. r = CAG.fromFakeCSG(r);
  6891. r = r.canonicalized();
  6892. return r;
  6893. },
  6894. transform: function(matrix4x4) {
  6895. var ismirror = matrix4x4.isMirroring();
  6896. var newsides = this.sides.map(function(side) {
  6897. return side.transform(matrix4x4);
  6898. });
  6899. var result = CAG.fromSides(newsides);
  6900. if (ismirror) {
  6901. result = result.flipped();
  6902. }
  6903. return result;
  6904. },
  6905. taper: function(vector, factor) {
  6906. if (factor <= 0)
  6907. factor = 0.0001;
  6908. var bounds = this.getBounds();
  6909. var max_distance = 0;
  6910. var start_pos = 0;
  6911. if (vector[0]) {
  6912. max_distance = bounds[1].x - bounds[0].x;
  6913. start_pos = bounds[0].x
  6914. }
  6915. else {
  6916. max_distance = bounds[1].y - bounds[0].y;
  6917. start_pos = bounds[0].y;
  6918. }
  6919. var newSides = [];
  6920. var newVert = [];
  6921. for (var i = 0; i < this.sides.length; i++) {
  6922. var pt = this.sides[i];
  6923. if (vector[0]) {
  6924. // taper along X axis
  6925. newVert[0] = [pt.vertex0.pos.x, pt.vertex0.pos.y * (1 + (factor - 1) * (pt.vertex0.pos.x - start_pos)/max_distance)];
  6926. newVert[1] = [pt.vertex1.pos.x, pt.vertex1.pos.y * (1 + (factor - 1) * (pt.vertex1.pos.x - start_pos)/max_distance)];
  6927. }
  6928. if (vector[1]) {
  6929. // taper along Y axis
  6930. newVert[0] = [pt.vertex0.pos.x * (1 + (factor - 1) * (pt.vertex0.pos.y - start_pos)/max_distance), pt.vertex0.pos.y];
  6931. newVert[1] = [pt.vertex1.pos.x * (1 + (factor - 1) * (pt.vertex1.pos.y - start_pos)/max_distance), pt.vertex1.pos.y];
  6932. }
  6933. var newSide = new CAG.Side(new CAG.Vertex(new CSG.Vector2D(newVert[0])), new CAG.Vertex(new CSG.Vector2D(newVert[1])));
  6934. newSides.push(newSide);
  6935. }
  6936. var newCAG = CAG.fromSides(newSides);
  6937. return newCAG;
  6938. },
  6939. // see http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/ :
  6940. // Area of the polygon. For a counter clockwise rotating polygon the area is positive, otherwise negative
  6941. // Note(bebbi): this looks wrong. See polygon getArea()
  6942. area: function() {
  6943. var polygonArea = 0;
  6944. this.sides.map(function(side) {
  6945. polygonArea += side.vertex0.pos.cross(side.vertex1.pos);
  6946. });
  6947. polygonArea *= 0.5;
  6948. return polygonArea;
  6949. },
  6950. flipped: function() {
  6951. var newsides = this.sides.map(function(side) {
  6952. return side.flipped();
  6953. });
  6954. newsides.reverse();
  6955. return CAG.fromSides(newsides);
  6956. },
  6957. getBounds: function() {
  6958. var minpoint;
  6959. if (this.sides.length === 0) {
  6960. minpoint = new CSG.Vector2D(0, 0);
  6961. } else {
  6962. minpoint = this.sides[0].vertex0.pos;
  6963. }
  6964. var maxpoint = minpoint;
  6965. this.sides.map(function(side) {
  6966. minpoint = minpoint.min(side.vertex0.pos);
  6967. minpoint = minpoint.min(side.vertex1.pos);
  6968. maxpoint = maxpoint.max(side.vertex0.pos);
  6969. maxpoint = maxpoint.max(side.vertex1.pos);
  6970. });
  6971. return [minpoint, maxpoint];
  6972. },
  6973. isSelfIntersecting: function(debug) {
  6974. var numsides = this.sides.length;
  6975. for (var i = 0; i < numsides; i++) {
  6976. var side0 = this.sides[i];
  6977. for (var ii = i + 1; ii < numsides; ii++) {
  6978. var side1 = this.sides[ii];
  6979. if (CAG.linesIntersect(side0.vertex0.pos, side0.vertex1.pos, side1.vertex0.pos, side1.vertex1.pos)) {
  6980. if (debug) { OpenJsCad.log(side0); OpenJsCad.log(side1);}
  6981. return true;
  6982. }
  6983. }
  6984. }
  6985. return false;
  6986. },
  6987. expandedShell: function(radius, resolution) {
  6988. resolution = resolution || 8;
  6989. if (resolution < 4) resolution = 4;
  6990. var cags = [];
  6991. var pointmap = {};
  6992. var cag = this.canonicalized();
  6993. cag.sides.map(function(side) {
  6994. var d = side.vertex1.pos.minus(side.vertex0.pos);
  6995. var dl = d.length();
  6996. if (dl > 1e-5) {
  6997. d = d.times(1.0 / dl);
  6998. var normal = d.normal().times(radius);
  6999. var shellpoints = [
  7000. side.vertex1.pos.plus(normal),
  7001. side.vertex1.pos.minus(normal),
  7002. side.vertex0.pos.minus(normal),
  7003. side.vertex0.pos.plus(normal)
  7004. ];
  7005. // var newcag = CAG.fromPointsNoCheck(shellpoints);
  7006. var newcag = CAG.fromPoints(shellpoints);
  7007. cags.push(newcag);
  7008. for (var step = 0; step < 2; step++) {
  7009. var p1 = (step === 0) ? side.vertex0.pos : side.vertex1.pos;
  7010. var p2 = (step === 0) ? side.vertex1.pos : side.vertex0.pos;
  7011. var tag = p1.x + " " + p1.y;
  7012. if (!(tag in pointmap)) {
  7013. pointmap[tag] = [];
  7014. }
  7015. pointmap[tag].push({
  7016. "p1": p1,
  7017. "p2": p2
  7018. });
  7019. }
  7020. }
  7021. });
  7022. for (var tag in pointmap) {
  7023. var m = pointmap[tag];
  7024. var angle1, angle2;
  7025. var pcenter = m[0].p1;
  7026. if (m.length == 2) {
  7027. var end1 = m[0].p2;
  7028. var end2 = m[1].p2;
  7029. angle1 = end1.minus(pcenter).angleDegrees();
  7030. angle2 = end2.minus(pcenter).angleDegrees();
  7031. if (angle2 < angle1) angle2 += 360;
  7032. if (angle2 >= (angle1 + 360)) angle2 -= 360;
  7033. if (angle2 < angle1 + 180) {
  7034. var t = angle2;
  7035. angle2 = angle1 + 360;
  7036. angle1 = t;
  7037. }
  7038. angle1 += 90;
  7039. angle2 -= 90;
  7040. } else {
  7041. angle1 = 0;
  7042. angle2 = 360;
  7043. }
  7044. var fullcircle = (angle2 > angle1 + 359.999);
  7045. if (fullcircle) {
  7046. angle1 = 0;
  7047. angle2 = 360;
  7048. }
  7049. if (angle2 > (angle1 + 1e-5)) {
  7050. var points = [];
  7051. if (!fullcircle) {
  7052. points.push(pcenter);
  7053. }
  7054. var numsteps = Math.round(resolution * (angle2 - angle1) / 360);
  7055. if (numsteps < 1) numsteps = 1;
  7056. for (var step = 0; step <= numsteps; step++) {
  7057. var angle = angle1 + step / numsteps * (angle2 - angle1);
  7058. if (step == numsteps) angle = angle2; // prevent rounding errors
  7059. var point = pcenter.plus(CSG.Vector2D.fromAngleDegrees(angle).times(radius));
  7060. if ((!fullcircle) || (step > 0)) {
  7061. points.push(point);
  7062. }
  7063. }
  7064. var newcag = CAG.fromPointsNoCheck(points);
  7065. cags.push(newcag);
  7066. }
  7067. }
  7068. var result = new CAG();
  7069. result = result.union(cags);
  7070. return result;
  7071. },
  7072. expand: function(radius, resolution) {
  7073. var result = this.union(this.expandedShell(radius, resolution));
  7074. return result;
  7075. },
  7076. contract: function(radius, resolution) {
  7077. var result = this.subtract(this.expandedShell(radius, resolution));
  7078. return result;
  7079. },
  7080. // extrude the CAG in a certain plane.
  7081. // Giving just a plane is not enough, multiple different extrusions in the same plane would be possible
  7082. // by rotating around the plane's origin. An additional right-hand vector should be specified as well,
  7083. // and this is exactly a CSG.OrthoNormalBasis.
  7084. // orthonormalbasis: characterizes the plane in which to extrude
  7085. // depth: thickness of the extruded shape. Extrusion is done symmetrically above and below the plane.
  7086. extrudeInOrthonormalBasis: function(orthonormalbasis, depth) {
  7087. // first extrude in the regular Z plane:
  7088. if (!(orthonormalbasis instanceof CSG.OrthoNormalBasis)) {
  7089. throw new Error("extrudeInPlane: the first parameter should be a CSG.OrthoNormalBasis");
  7090. }
  7091. var extruded = this.extrude({
  7092. offset: [0, 0, depth]
  7093. });
  7094. var matrix = orthonormalbasis.getInverseProjectionMatrix();
  7095. extruded = extruded.transform(matrix);
  7096. return extruded;
  7097. },
  7098. // Extrude in a standard cartesian plane, specified by two axis identifiers. Each identifier can be
  7099. // one of ["X","Y","Z","-X","-Y","-Z"]
  7100. // The 2d x axis will map to the first given 3D axis, the 2d y axis will map to the second.
  7101. // See CSG.OrthoNormalBasis.GetCartesian for details.
  7102. extrudeInPlane: function(axis1, axis2, depth) {
  7103. return this.extrudeInOrthonormalBasis(CSG.OrthoNormalBasis.GetCartesian(axis1, axis2), depth);
  7104. },
  7105. // extruded=cag.extrude({offset: [0,0,10], twistangle: 360, twiststeps: 100, scale: 1});
  7106. // linear extrusion of 2D shape, with optional twist
  7107. // The 2d shape is placed in in z=0 plane and extruded into direction <offset> (a CSG.Vector3D)
  7108. // The final face is rotated <twistangle> degrees. Rotation is done around the origin of the 2d shape (i.e. x=0, y=0)
  7109. // twiststeps determines the resolution of the twist (should be >= 1)
  7110. // returns a CSG object
  7111. extrude: function(options) {
  7112. if (this.sides.length == 0) {
  7113. // empty!
  7114. return new CSG();
  7115. }
  7116. var offsetVector = CSG.parseOptionAs3DVector(options, "offset", [0, 0, 1]);
  7117. var twistangle = CSG.parseOptionAsFloat(options, "twistangle", 0);
  7118. var twiststeps = CSG.parseOptionAsInt(options, "twiststeps", CSG.defaultResolution3D);
  7119. var scale = CSG.parseOptionAs2DVector(options, "scale", [1,1]);
  7120. // to match openscad behavior, set scale values of less than 0 to 0.0001 (because 0 seems to break interactions with other CSGs).
  7121. var xscale = scale.x;
  7122. var yscale = scale.y;
  7123. if (xscale < 0.0001)
  7124. xscale = 0.0001;
  7125. if (yscale < 0.0001)
  7126. yscale = 0.0001;
  7127. // console.log("scale option is x: " + xscale + " y: " + yscale);
  7128. if (offsetVector.z == 0) {
  7129. throw('offset cannot be orthogonal to Z axis');
  7130. }
  7131. if (twistangle == 0 || twiststeps < 1) {
  7132. twiststeps = 1;
  7133. }
  7134. var normalVector = CSG.Vector3D.Create(0, 1, 0);
  7135. var polygons = [];
  7136. // bottom and top
  7137. polygons = polygons.concat(this._toPlanePolygons({translation: [0, 0, 0],
  7138. normalVector: normalVector, flipped: !(offsetVector.z < 0)}));
  7139. polygons = polygons.concat(this._toPlanePolygons({translation: offsetVector,
  7140. normalVector: normalVector.rZ(twistangle), flipped: offsetVector.z < 0}));
  7141. // walls
  7142. for (var i = 0; i < twiststeps; i++) {
  7143. var c1 = new CSG.Connector(offsetVector.times(i / twiststeps), [0, 0, offsetVector.z],
  7144. normalVector.rZ(i * twistangle/twiststeps));
  7145. var c2 = new CSG.Connector(offsetVector.times((i + 1) / twiststeps), [0, 0, offsetVector.z],
  7146. normalVector.rZ((i + 1) * twistangle/twiststeps));
  7147. polygons = polygons.concat(this._toWallPolygons({toConnector1: c1, toConnector2: c2}));
  7148. }
  7149. // go through all polygons. Scale x and y points based on scale * z_point / offsetVector.z
  7150. // console.log(polygons);
  7151. var newPolys = [];
  7152. // console.log(offsetVector.z);
  7153. var newVert = [];
  7154. for (var i = 0; i < polygons.length; i++) {
  7155. // console.log("scaling a polygon");
  7156. newVert = [];
  7157. for (var j = 0; j < polygons[i].vertices.length; j++) {
  7158. var z = polygons[i].vertices[j].pos.z;
  7159. var x = polygons[i].vertices[j].pos._x * ( 1 + ((xscale - 1) * z / offsetVector.z));
  7160. var y = polygons[i].vertices[j].pos._y * (1 + ((yscale - 1) * z / offsetVector.z));
  7161. newVert[j] = [x,y,z];
  7162. }
  7163. newPolys[i] = new CSG.Polygon.createFromPoints(newVert);
  7164. newPolys[i].shared = polygons[i].shared;
  7165. }
  7166. return CSG.fromPolygons(newPolys);
  7167. },
  7168. /*
  7169. * extrude CAG to 3d object by rotating the origin around the y axis
  7170. * (and turning everything into XY plane)
  7171. * arguments: options dict with angle and resolution, both optional
  7172. */
  7173. rotateExtrude: function(options) {
  7174. var alpha = CSG.parseOptionAsFloat(options, "angle", 360);
  7175. var resolution = CSG.parseOptionAsInt(options, "resolution", CSG.defaultResolution3D);
  7176. var EPS = 1e-5;
  7177. alpha = alpha > 360 ? alpha % 360 : alpha;
  7178. var origin = [0, 0, 0];
  7179. var axisV = CSG.Vector3D.Create(0, 1, 0);
  7180. var normalV = [0, 0, 1];
  7181. var polygons = [];
  7182. // planes only needed if alpha > 0
  7183. var connS = new CSG.Connector(origin, axisV, normalV);
  7184. if (alpha > 0 && alpha < 360) {
  7185. // we need to rotate negative to satisfy wall function condition of
  7186. // building in the direction of axis vector
  7187. var connE = new CSG.Connector(origin, axisV.rZ(-alpha), normalV);
  7188. polygons = polygons.concat(
  7189. this._toPlanePolygons({toConnector: connS, flipped: true}));
  7190. polygons = polygons.concat(
  7191. this._toPlanePolygons({toConnector: connE}));
  7192. }
  7193. var connT1 = connS, connT2;
  7194. var step = alpha/resolution;
  7195. for (var a = step; a <= alpha + EPS; a += step) {
  7196. connT2 = new CSG.Connector(origin, axisV.rZ(-a), normalV);
  7197. polygons = polygons.concat(this._toWallPolygons(
  7198. {toConnector1: connT1, toConnector2: connT2}));
  7199. connT1 = connT2;
  7200. }
  7201. return CSG.fromPolygons(polygons).reTesselated();
  7202. },
  7203. // Added rotate_extrude as a method so that the openSCAD function can be used in BlockSCAD.
  7204. // rotate_extrude({fn = 10});
  7205. // $fn passed to rotate_extrude determines the number of "sides" of the torus produced (3 makes
  7206. // a triangle, four makes a square, etc) which is different from the number of sides of the
  7207. // 2-D shape that is being rotated. So, you can rotate a triangle (a circle with $fn=3) to look like a
  7208. // square donut (rotate_extrude gets $fn=4) with a triangle cross-section. That's always useful.
  7209. // rotate_extrude works by flipping the 2D shape around the X axis 90 deg. and then rotating that around the
  7210. // Z axis 360 degrees. The code from OpenJScad didn't play nicely with shapes that extend to the left
  7211. // of the X-axis. Openscad renders shapes like this okay using F5, but not at all using CGAL (F6).
  7212. // I'm going to hide this problem by transforming the shape passed into rotate_extrude. I'll chop off
  7213. // anything to the left of the x-axis (+ 0.001, because the rotate extrude barfs if the shape has a
  7214. // line that is exactly on the x-axis, mirror that chopped-off bit across the x-axis, then union the two
  7215. // shapes, then rotate the result. This is exactly what you would get if you were able to cleanly
  7216. // rotate a shape that crossed the x-axis except for a 0.001 radius hole along the x-axis. -Jennie
  7217. // NEW - I am assuming that the shape starts at the origin, and that the user will give a translation value if they wish.
  7218. rotate_extrude: function(options) {
  7219. if(this.sides.length == 0) {
  7220. // empty!
  7221. return new CSG();
  7222. }
  7223. // console.log(options);
  7224. var fn = CSG.parseOptionAsInt(options, "faces", 10);
  7225. var twist = CSG.parseOptionAsInt(options, "twist", 0);
  7226. var xtr = Math.abs(CSG.parseOptionAsInt(options, "radius", 0));
  7227. var twist_steps = CSG.parseOptionAsInt(options, "tsteps", 0);
  7228. //console.log("faces twist radius twist_steps",fn,", ",twist,", ",xtr,", ",twist_steps);
  7229. if(fn < 3) fn = 3; // 3 is the fewest possible number of faces - a triangle
  7230. // figure out how many twist steps per face (side) - must be an integer that is at least one
  7231. var TSPS = Math.ceil(twist_steps / fn);
  7232. // console.log("TSPS=",TSPS);
  7233. if (TSPS < 1) TSPS = 1;
  7234. // rotate_extrude will not work if there is a line exactly on the x-axis, so subtract off to 0.001.
  7235. var baad_square_pts = [[0.001,100000],[-200000,100000],[-200000,-100000],[0.001,-100000]];
  7236. var good_square_pts = [[-0.001,100000],[200000,100000],[200000,-100000],[-0.001,-100000]];
  7237. var good_square = CAG.fromPoints(good_square_pts);
  7238. var baad_square = CAG.fromPoints(baad_square_pts);
  7239. // to find out what the bad and good shape are, I should first tr the original shape.
  7240. var startshape = this.tr([xtr,0,0]);
  7241. var badshape = startshape.subtract(good_square); // is there anything left of the x-axis?
  7242. var safeshape = startshape.subtract(baad_square); // here is a shape that can be rotated!
  7243. if (badshape.sides.length != 0) {
  7244. // mirror the bad stuff across the x-axis, and union it with the safe stuff
  7245. var o = badshape.mirroredX().union(safeshape);
  7246. } else {
  7247. var o = safeshape;
  7248. }
  7249. // console.log(o);
  7250. // console.log(orig_shape);
  7251. // now tr it back to the center for getting the rotated copies.
  7252. o = o.tr([-xtr,0,0]);
  7253. var shape_stage = [];
  7254. for (var i=0; i < fn + 2; i++) {
  7255. n = new CSG.Matrix4x4.rotationZ((twist/360) * i/fn*360); // attempting to add twist - JY
  7256. shape_stage[i] = o.transform(n);
  7257. shape_stage[i] = shape_stage[i].tr([xtr, 0, 0]);
  7258. shape_stage[i] = shape_stage[i].canonicalized();
  7259. }
  7260. // shape_stage[i] = shape_stage[0];
  7261. // Now that I've calculated the twisted shapes, do I move the original shape over?
  7262. o = o.tr([xtr,0,0]);
  7263. var ps = [];
  7264. for(var i=0; i<fn; i++) {
  7265. // o.{x,y} -> rotate([0,0,i:0..360], obj->{o.x,0,o.y})
  7266. // console.log("in rotate extrude, here are two x coords");
  7267. // console.log(shape_stage[i]);
  7268. for(var j=0; j<o.sides.length; j++) {
  7269. // has o.sides[j].vertex{0,1}.pos (only x,y)
  7270. var p_o = [];
  7271. var p = [];
  7272. var pN = [];
  7273. var m;
  7274. var n;
  7275. m = new CSG.Matrix4x4.rotationZ(i/fn*360);
  7276. n = new CSG.Matrix4x4.rotationZ((i+1)/fn*360);
  7277. p[0] = new CSG.Vector3D(shape_stage[i].sides[j].vertex0.pos.x,0,shape_stage[i].sides[j].vertex0.pos.y);
  7278. p[1] = new CSG.Vector3D(shape_stage[i].sides[j].vertex1.pos.x,0,shape_stage[i].sides[j].vertex1.pos.y);
  7279. p[2] = new CSG.Vector3D(shape_stage[i+1].sides[j].vertex1.pos.x,0,shape_stage[i+1].sides[j].vertex1.pos.y);
  7280. p[3] = new CSG.Vector3D(shape_stage[i+1].sides[j].vertex0.pos.x,0,shape_stage[i+1].sides[j].vertex0.pos.y);
  7281. // // original points?
  7282. // p_o[0] = new CSG.Vector3D(o.sides[j].vertex0.pos.x,0,o.sides[j].vertex0.pos.y);
  7283. // p_o[1] = new CSG.Vector3D(o.sides[j].vertex1.pos.x,0,o.sides[j].vertex1.pos.y);
  7284. // p_o[2] = new CSG.Vector3D(o.sides[j].vertex1.pos.x,0,o.sides[j].vertex1.pos.y);
  7285. // p_o[3] = new CSG.Vector3D(o.sides[j].vertex0.pos.x,0,o.sides[j].vertex0.pos.y);
  7286. //console.log("p[0] is: ", p[0]);
  7287. // console.log(o.sides[j].vertex0.pos.x, ", ", o.sides[j].vertex0.pos.y);
  7288. // console.log(o.sides[j].vertex1.pos.x, ", ", o.sides[j].vertex1.pos.y);
  7289. p[0] = m.rightMultiply1x3Vector(p[0]);
  7290. p[1] = m.rightMultiply1x3Vector(p[1]);
  7291. p[2] = n.rightMultiply1x3Vector(p[2]);
  7292. p[3] = n.rightMultiply1x3Vector(p[3]);
  7293. // console.log("regular points",p[0],p[1],p[2],p[3]);
  7294. var dx = (p[3]._x - p[0]._x)/TSPS;
  7295. var dy = (p[3]._y - p[0]._y)/TSPS;
  7296. var dz = (p[3]._z - p[0]._z)/TSPS;
  7297. var dx2 = (p[2]._x - p[1]._x)/TSPS;
  7298. var dy2 = (p[2]._y - p[1]._y)/TSPS;
  7299. var dz2 = (p[2]._z - p[1]._z)/TSPS;
  7300. // console.log("dx=",dx,", dy=",dy ,", dz=",dz );
  7301. // console.log("dx2=",dx2,", dy2=",dy2 ,", dz2=",dz2 );
  7302. // console.log(p[0]);
  7303. for (var v=0; v<TSPS; v++) {
  7304. // console.log("i = ",i,",j = ",j,",v = ",v,", TSPS = ",TSPS);
  7305. // I need to make a series of points.
  7306. // this series of points should include the start and end points.
  7307. pN[0] = new CSG.Vector3D(p[0]._x + v*dx,p[0]._y + v*dy, p[0]._z + v*dz);
  7308. pN[1] = new CSG.Vector3D(p[1]._x + v*dx2,p[1]._y + v*dy2, p[1]._z + v*dz2);
  7309. pN[2] = new CSG.Vector3D(p[1]._x + (v+1)*dx2,p[1]._y + (v+1)*dy2, p[1]._z + (v+1)*dz2);
  7310. pN[3] = new CSG.Vector3D(p[0]._x + (v+1)*dx,p[0]._y + (v+1)*dy, p[0]._z + (v+1)*dz);
  7311. // console.log("new points",pN[0],pN[1],pN[2],pN[3]);
  7312. var p1 = new CSG.Polygon([
  7313. new CSG.Vertex(pN[0]),
  7314. new CSG.Vertex(pN[1]),
  7315. new CSG.Vertex(pN[2]),
  7316. // with a twist, we cannot make a square anymore. Two triangles is okay.
  7317. //new CSG.Vertex(p[3]), // we make a square polygon (instead of 2 triangles)
  7318. ]);
  7319. var p2 = new CSG.Polygon([
  7320. new CSG.Vertex(pN[0]),
  7321. new CSG.Vertex(pN[2]),
  7322. new CSG.Vertex(pN[3]),
  7323. ]);
  7324. ps.push(p1);
  7325. ps.push(p2);
  7326. // console.log("added two polygons");
  7327. }
  7328. //echo("i="+i,i/fn*360,"j="+j);
  7329. }
  7330. }
  7331. // ps.push(ps[0]);
  7332. // ps.push(ps[1]);
  7333. var result = CSG.fromPolygons(ps);
  7334. result = result.reTesselated();
  7335. result = result.canonicalized();
  7336. return result;
  7337. }, // end rotate_extrude
  7338. hull: function(cag) {
  7339. // console.log('in CAG hull');
  7340. // from http://www.psychedelicdevelopment.com/grahamscan/
  7341. // see also at https://github.com/bkiers/GrahamScan/blob/master/src/main/cg/GrahamScan.java
  7342. CAG.ConvexHullPoint = function(i, a, d) {
  7343. this.index = i;
  7344. this.angle = a;
  7345. this.distance = d;
  7346. this.compare = function(p) {
  7347. if (this.angle<p.angle)
  7348. return -1;
  7349. else if (this.angle>p.angle)
  7350. return 1;
  7351. else {
  7352. if (this.distance<p.distance)
  7353. return -1;
  7354. else if (this.distance>p.distance)
  7355. return 1;
  7356. }
  7357. return 0;
  7358. }
  7359. }
  7360. CAG.ConvexHull = function() {
  7361. this.points = null;
  7362. this.indices = null;
  7363. this.getIndices = function() {
  7364. return this.indices;
  7365. }
  7366. this.clear = function() {
  7367. this.indices = null;
  7368. this.points = null;
  7369. }
  7370. this.ccw = function(p1, p2, p3) {
  7371. var ccw = (this.points[p2].x - this.points[p1].x)*(this.points[p3].y - this.points[p1].y) -
  7372. (this.points[p2].y - this.points[p1].y)*(this.points[p3].x - this.points[p1].x);
  7373. if(ccw<1e-5) // we need this, otherwise sorting never ends, see https://github.com/Spiritdude/OpenJSCAD.org/issues/18
  7374. return 0
  7375. return ccw;
  7376. }
  7377. this.angle = function(o, a) {
  7378. //return Math.atan((this.points[a].y-this.points[o].y) / (this.points[a].x - this.points[o].x));
  7379. return Math.atan2((this.points[a].y-this.points[o].y), (this.points[a].x - this.points[o].x));
  7380. }
  7381. this.distance = function(a, b) {
  7382. return ((this.points[b].x-this.points[a].x)*(this.points[b].x-this.points[a].x)+
  7383. (this.points[b].y-this.points[a].y)*(this.points[b].y-this.points[a].y));
  7384. }
  7385. this.compute = function(_points) {
  7386. this.indices=null;
  7387. if (_points.length<3)
  7388. return;
  7389. this.points=_points;
  7390. // Find the lowest point
  7391. var min = 0;
  7392. for(var i = 1; i < this.points.length; i++) {
  7393. if(this.points[i].y==this.points[min].y) {
  7394. if(this.points[i].x<this.points[min].x)
  7395. min = i;
  7396. }
  7397. else if(this.points[i].y<this.points[min].y)
  7398. min = i;
  7399. }
  7400. // Calculate angle and distance from base
  7401. var al = new Array();
  7402. var ang = 0.0;
  7403. var dist = 0.0;
  7404. for (i = 0; i<this.points.length; i++) {
  7405. if (i==min)
  7406. continue;
  7407. ang = this.angle(min, i);
  7408. if (ang<0)
  7409. ang += Math.PI;
  7410. dist = this.distance(min, i);
  7411. al.push(new CAG.ConvexHullPoint(i, ang, dist));
  7412. }
  7413. al.sort(function (a, b) { return a.compare(b); });
  7414. // Create stack
  7415. var stack = new Array(this.points.length+1);
  7416. var j = 2;
  7417. for(i = 0; i<this.points.length; i++) {
  7418. if(i==min)
  7419. continue;
  7420. stack[j] = al[j-2].index;
  7421. j++;
  7422. }
  7423. stack[0] = stack[this.points.length];
  7424. stack[1] = min;
  7425. var tmp;
  7426. var M = 2;
  7427. for(i = 3; i<=this.points.length; i++) {
  7428. while(this.ccw(stack[M-1], stack[M], stack[i]) <= 0)
  7429. M--;
  7430. M++;
  7431. tmp = stack[i];
  7432. stack[i] = stack[M];
  7433. stack[M] = tmp;
  7434. }
  7435. this.indices = new Array(M);
  7436. for (i = 0; i<M; i++) {
  7437. this.indices[i] = stack[i+1];
  7438. }
  7439. } // end this.compute
  7440. } // end ConvexHull
  7441. var top_guy = this;
  7442. var other_cags = cag;
  7443. var cags = [];
  7444. cags.push(top_guy);
  7445. for(var i = 0; i < other_cags.length; i++) {
  7446. cags.push(other_cags[i]);
  7447. }
  7448. var pts = [];
  7449. var done = [];
  7450. for(var i=0; i<cags.length; i++) { // extract all points of the CAG in the argument list
  7451. var cag = cags[i];
  7452. if(!(cag instanceof CAG)) {
  7453. // console.log("found a CSG in the CAG hull");
  7454. throw("ERROR: don't mix 2D and 3D shapes in hull");
  7455. //return new CAG();
  7456. }
  7457. for(var j=0; j<cag.sides.length; j++) {
  7458. var x = cag.sides[j].vertex0.pos.x;
  7459. var y = cag.sides[j].vertex0.pos.y;
  7460. if(done[''+x+','+y]) // avoid some coord to appear multiple times
  7461. continue;
  7462. pts.push({ x:x, y:y });
  7463. done[''+x+','+y]++;
  7464. //echo(x,y);
  7465. }
  7466. }
  7467. //echo(pts.length+" points in",pts);
  7468. var hull = new CAG.ConvexHull();
  7469. hull.compute(pts);
  7470. var indices = hull.getIndices();
  7471. if(indices&&indices.length>0) {
  7472. var ch = [];
  7473. for(var i=0; i<indices.length; i++) {
  7474. ch.push(pts[indices[i]]);
  7475. //echo(pts[indices[i]]);
  7476. }
  7477. //echo(ch.length+" points out",ch);
  7478. return CAG.fromPoints(ch);
  7479. //return CAG.fromPointsNoCheck(ch);
  7480. }
  7481. }, // end hull (CAG version)
  7482. // check if we are a valid CAG (for debugging)
  7483. // NOTE(bebbi) uneven side count doesn't work because rounding with EPS isn't taken into account
  7484. check: function() {
  7485. var EPS = 1e-5;
  7486. var errors = [];
  7487. if (this.isSelfIntersecting(true)) {
  7488. errors.push("Self intersects");
  7489. }
  7490. var pointcount = {};
  7491. this.sides.map(function(side) {
  7492. function mappoint(p) {
  7493. var tag = p.x + " " + p.y;
  7494. if (!(tag in pointcount)) pointcount[tag] = 0;
  7495. pointcount[tag] ++;
  7496. }
  7497. mappoint(side.vertex0.pos);
  7498. mappoint(side.vertex1.pos);
  7499. });
  7500. for (var tag in pointcount) {
  7501. var count = pointcount[tag];
  7502. if (count & 1) {
  7503. errors.push("Uneven number of sides (" + count + ") for point " + tag);
  7504. }
  7505. }
  7506. var area = this.area();
  7507. if (area < EPS*EPS) {
  7508. errors.push("Area is " + area);
  7509. }
  7510. if (errors.length > 0) {
  7511. var ertxt = "";
  7512. errors.map(function(err) {
  7513. ertxt += err + "\n";
  7514. });
  7515. throw new Error(ertxt);
  7516. }
  7517. },
  7518. canonicalized: function() {
  7519. if (this.isCanonicalized) {
  7520. return this;
  7521. } else {
  7522. var factory = new CAG.fuzzyCAGFactory();
  7523. var result = factory.getCAG(this);
  7524. result.isCanonicalized = true;
  7525. return result;
  7526. }
  7527. },
  7528. toCompactBinary: function() {
  7529. var cag = this.canonicalized();
  7530. var numsides = cag.sides.length;
  7531. var vertexmap = {};
  7532. var vertices = [];
  7533. var numvertices = 0;
  7534. var sideVertexIndices = new Uint32Array(2 * numsides);
  7535. var sidevertexindicesindex = 0;
  7536. cag.sides.map(function(side) {
  7537. [side.vertex0, side.vertex1].map(function(v) {
  7538. var vertextag = v.getTag();
  7539. var vertexindex;
  7540. if (!(vertextag in vertexmap)) {
  7541. vertexindex = numvertices++;
  7542. vertexmap[vertextag] = vertexindex;
  7543. vertices.push(v);
  7544. } else {
  7545. vertexindex = vertexmap[vertextag];
  7546. }
  7547. sideVertexIndices[sidevertexindicesindex++] = vertexindex;
  7548. });
  7549. });
  7550. var vertexData = new Float64Array(numvertices * 2);
  7551. var verticesArrayIndex = 0;
  7552. vertices.map(function(v) {
  7553. var pos = v.pos;
  7554. vertexData[verticesArrayIndex++] = pos._x;
  7555. vertexData[verticesArrayIndex++] = pos._y;
  7556. });
  7557. var result = {
  7558. 'class': "CAG",
  7559. sideVertexIndices: sideVertexIndices,
  7560. vertexData: vertexData
  7561. };
  7562. return result;
  7563. },
  7564. getOutlinePaths: function() {
  7565. var cag = this.canonicalized();
  7566. var sideTagToSideMap = {};
  7567. var startVertexTagToSideTagMap = {};
  7568. cag.sides.map(function(side) {
  7569. var sidetag = side.getTag();
  7570. sideTagToSideMap[sidetag] = side;
  7571. var startvertextag = side.vertex0.getTag();
  7572. if (!(startvertextag in startVertexTagToSideTagMap)) {
  7573. startVertexTagToSideTagMap[startvertextag] = [];
  7574. }
  7575. startVertexTagToSideTagMap[startvertextag].push(sidetag);
  7576. });
  7577. var paths = [];
  7578. while (true) {
  7579. var startsidetag = null;
  7580. for (var aVertexTag in startVertexTagToSideTagMap) {
  7581. var sidesForThisVertex = startVertexTagToSideTagMap[aVertexTag];
  7582. startsidetag = sidesForThisVertex[0];
  7583. sidesForThisVertex.splice(0, 1);
  7584. if (sidesForThisVertex.length === 0) {
  7585. delete startVertexTagToSideTagMap[aVertexTag];
  7586. }
  7587. break;
  7588. }
  7589. if (startsidetag === null) break; // we've had all sides
  7590. var connectedVertexPoints = [];
  7591. var sidetag = startsidetag;
  7592. var thisside = sideTagToSideMap[sidetag];
  7593. var startvertextag = thisside.vertex0.getTag();
  7594. while (true) {
  7595. connectedVertexPoints.push(thisside.vertex0.pos);
  7596. var nextvertextag = thisside.vertex1.getTag();
  7597. if (nextvertextag == startvertextag) break; // we've closed the polygon
  7598. if (!(nextvertextag in startVertexTagToSideTagMap)) {
  7599. throw new Error("Area is not closed!");
  7600. }
  7601. var nextpossiblesidetags = startVertexTagToSideTagMap[nextvertextag];
  7602. var nextsideindex = -1;
  7603. if (nextpossiblesidetags.length == 1) {
  7604. nextsideindex = 0;
  7605. } else {
  7606. // more than one side starting at the same vertex. This means we have
  7607. // two shapes touching at the same corner
  7608. var bestangle = null;
  7609. var thisangle = thisside.direction().angleDegrees();
  7610. for (var sideindex = 0; sideindex < nextpossiblesidetags.length; sideindex++) {
  7611. var nextpossiblesidetag = nextpossiblesidetags[sideindex];
  7612. var possibleside = sideTagToSideMap[nextpossiblesidetag];
  7613. var angle = possibleside.direction().angleDegrees();
  7614. var angledif = angle - thisangle;
  7615. if (angledif < -180) angledif += 360;
  7616. if (angledif >= 180) angledif -= 360;
  7617. if ((nextsideindex < 0) || (angledif > bestangle)) {
  7618. nextsideindex = sideindex;
  7619. bestangle = angledif;
  7620. }
  7621. }
  7622. }
  7623. var nextsidetag = nextpossiblesidetags[nextsideindex];
  7624. nextpossiblesidetags.splice(nextsideindex, 1);
  7625. if (nextpossiblesidetags.length === 0) {
  7626. delete startVertexTagToSideTagMap[nextvertextag];
  7627. }
  7628. thisside = sideTagToSideMap[nextsidetag];
  7629. } // inner loop
  7630. var path = new CSG.Path2D(connectedVertexPoints, true);
  7631. paths.push(path);
  7632. } // outer loop
  7633. return paths;
  7634. },
  7635. /*
  7636. cag = cag.overCutInsideCorners(cutterradius);
  7637. Using a CNC router it's impossible to cut out a true sharp inside corner. The inside corner
  7638. will be rounded due to the radius of the cutter. This function compensates for this by creating
  7639. an extra cutout at each inner corner so that the actual cut out shape will be at least as large
  7640. as needed.
  7641. */
  7642. overCutInsideCorners: function(cutterradius) {
  7643. var cag = this.canonicalized();
  7644. // for each vertex determine the 'incoming' side and 'outgoing' side:
  7645. var pointmap = {}; // tag => {pos: coord, from: [], to: []}
  7646. cag.sides.map(function(side) {
  7647. if (!(side.vertex0.getTag() in pointmap)) {
  7648. pointmap[side.vertex0.getTag()] = {
  7649. pos: side.vertex0.pos,
  7650. from: [],
  7651. to: []
  7652. };
  7653. }
  7654. pointmap[side.vertex0.getTag()].to.push(side.vertex1.pos);
  7655. if (!(side.vertex1.getTag() in pointmap)) {
  7656. pointmap[side.vertex1.getTag()] = {
  7657. pos: side.vertex1.pos,
  7658. from: [],
  7659. to: []
  7660. };
  7661. }
  7662. pointmap[side.vertex1.getTag()].from.push(side.vertex0.pos);
  7663. });
  7664. // overcut all sharp corners:
  7665. var cutouts = [];
  7666. for (var pointtag in pointmap) {
  7667. var pointobj = pointmap[pointtag];
  7668. if ((pointobj.from.length == 1) && (pointobj.to.length == 1)) {
  7669. // ok, 1 incoming side and 1 outgoing side:
  7670. var fromcoord = pointobj.from[0];
  7671. var pointcoord = pointobj.pos;
  7672. var tocoord = pointobj.to[0];
  7673. var v1 = pointcoord.minus(fromcoord).unit();
  7674. var v2 = tocoord.minus(pointcoord).unit();
  7675. var crossproduct = v1.cross(v2);
  7676. var isInnerCorner = (crossproduct < 0.001);
  7677. if (isInnerCorner) {
  7678. // yes it's a sharp corner:
  7679. var alpha = v2.angleRadians() - v1.angleRadians() + Math.PI;
  7680. if (alpha < 0) {
  7681. alpha += 2 * Math.PI;
  7682. } else if (alpha >= 2 * Math.PI) {
  7683. alpha -= 2 * Math.PI;
  7684. }
  7685. var midvector = v2.minus(v1).unit();
  7686. var circlesegmentangle = 30 / 180 * Math.PI; // resolution of the circle: segments of 30 degrees
  7687. // we need to increase the radius slightly so that our imperfect circle will contain a perfect circle of cutterradius
  7688. var radiuscorrected = cutterradius / Math.cos(circlesegmentangle / 2);
  7689. var circlecenter = pointcoord.plus(midvector.times(radiuscorrected));
  7690. // we don't need to create a full circle; a pie is enough. Find the angles for the pie:
  7691. var startangle = alpha + midvector.angleRadians();
  7692. var deltaangle = 2 * (Math.PI - alpha);
  7693. var numsteps = 2 * Math.ceil(deltaangle / circlesegmentangle / 2); // should be even
  7694. // build the pie:
  7695. var points = [circlecenter];
  7696. for (var i = 0; i <= numsteps; i++) {
  7697. var angle = startangle + i / numsteps * deltaangle;
  7698. var p = CSG.Vector2D.fromAngleRadians(angle).times(radiuscorrected).plus(circlecenter);
  7699. points.push(p);
  7700. }
  7701. cutouts.push(CAG.fromPoints(points));
  7702. }
  7703. }
  7704. }
  7705. var result = cag.subtract(cutouts);
  7706. return result;
  7707. }
  7708. };
  7709. CAG.Vertex = function(pos) {
  7710. this.pos = pos;
  7711. };
  7712. CAG.Vertex.fromObject = function(obj) {
  7713. return new CAG.Vertex(new CSG.Vector2D(obj.pos._x,obj.pos._y));
  7714. };
  7715. CAG.Vertex.prototype = {
  7716. toString: function() {
  7717. return "(" + this.pos.x.toFixed(2) + "," + this.pos.y.toFixed(2) + ")";
  7718. },
  7719. getTag: function() {
  7720. var result = this.tag;
  7721. if (!result) {
  7722. result = CSG.getTag();
  7723. this.tag = result;
  7724. }
  7725. return result;
  7726. }
  7727. };
  7728. CAG.Side = function(vertex0, vertex1) {
  7729. if (!(vertex0 instanceof CAG.Vertex)) throw new Error("Assertion failed");
  7730. if (!(vertex1 instanceof CAG.Vertex)) throw new Error("Assertion failed");
  7731. this.vertex0 = vertex0;
  7732. this.vertex1 = vertex1;
  7733. };
  7734. CAG.Side.fromObject = function(obj) {
  7735. var vertex0 = CAG.Vertex.fromObject(obj.vertex0);
  7736. var vertex1 = CAG.Vertex.fromObject(obj.vertex1);
  7737. return new CAG.Side(vertex0,vertex1);
  7738. };
  7739. CAG.Side._fromFakePolygon = function(polygon) {
  7740. polygon.vertices.forEach(function(v) {
  7741. if (!((v.pos.z >= -1.001) && (v.pos.z < -0.999)) && !((v.pos.z >= 0.999) && (v.pos.z < 1.001))) {
  7742. throw("Assertion failed: _fromFakePolygon expects abs z values of 1");
  7743. }
  7744. })
  7745. // this can happen based on union, seems to be residuals -
  7746. // return null and handle in caller
  7747. if (polygon.vertices.length < 4) {
  7748. return null;
  7749. }
  7750. var reverse = false;
  7751. var vert1Indices = [];
  7752. var pts2d = polygon.vertices.filter(function(v, i) {
  7753. if (v.pos.z > 0) {
  7754. vert1Indices.push(i);
  7755. return true;
  7756. }
  7757. })
  7758. .map(function(v) {
  7759. return new CSG.Vector2D(v.pos.x, v.pos.y);
  7760. });
  7761. if (pts2d.length != 2) {
  7762. throw('Assertion failed: _fromFakePolygon: not enough points found')
  7763. }
  7764. var d = vert1Indices[1] - vert1Indices[0];
  7765. if (d == 1 || d == 3) {
  7766. if (d == 1) {
  7767. pts2d.reverse();
  7768. }
  7769. } else {
  7770. throw('Assertion failed: _fromFakePolygon: unknown index ordering');
  7771. }
  7772. var result = new CAG.Side(new CAG.Vertex(pts2d[0]), new CAG.Vertex(pts2d[1]));
  7773. return result;
  7774. };
  7775. CAG.Side.prototype = {
  7776. toString: function() {
  7777. return this.vertex0 + " -> " + this.vertex1;
  7778. },
  7779. toPolygon3D: function(z0, z1) {
  7780. var vertices = [
  7781. new CSG.Vertex(this.vertex0.pos.toVector3D(z0)),
  7782. new CSG.Vertex(this.vertex1.pos.toVector3D(z0)),
  7783. new CSG.Vertex(this.vertex1.pos.toVector3D(z1)),
  7784. new CSG.Vertex(this.vertex0.pos.toVector3D(z1))
  7785. ];
  7786. return new CSG.Polygon(vertices);
  7787. },
  7788. transform: function(matrix4x4) {
  7789. var newp1 = this.vertex0.pos.transform(matrix4x4);
  7790. var newp2 = this.vertex1.pos.transform(matrix4x4);
  7791. return new CAG.Side(new CAG.Vertex(newp1), new CAG.Vertex(newp2));
  7792. },
  7793. flipped: function() {
  7794. return new CAG.Side(this.vertex1, this.vertex0);
  7795. },
  7796. direction: function() {
  7797. return this.vertex1.pos.minus(this.vertex0.pos);
  7798. },
  7799. getTag: function() {
  7800. var result = this.tag;
  7801. if (!result) {
  7802. result = CSG.getTag();
  7803. this.tag = result;
  7804. }
  7805. return result;
  7806. },
  7807. lengthSquared: function() {
  7808. var x = this.vertex1.pos.x - this.vertex0.pos.x,
  7809. y = this.vertex1.pos.y - this.vertex0.pos.y;
  7810. return x * x + y * y;
  7811. },
  7812. length: function() {
  7813. return Math.sqrt(this.lengthSquared());
  7814. }
  7815. };
  7816. //////////////////////////////////////
  7817. CAG.fuzzyCAGFactory = function() {
  7818. this.vertexfactory = new CSG.fuzzyFactory(2, 1e-5);
  7819. };
  7820. CAG.fuzzyCAGFactory.prototype = {
  7821. getVertex: function(sourcevertex) {
  7822. var elements = [sourcevertex.pos._x, sourcevertex.pos._y];
  7823. var result = this.vertexfactory.lookupOrCreate(elements, function(els) {
  7824. return sourcevertex;
  7825. });
  7826. return result;
  7827. },
  7828. getSide: function(sourceside) {
  7829. var vertex0 = this.getVertex(sourceside.vertex0);
  7830. var vertex1 = this.getVertex(sourceside.vertex1);
  7831. return new CAG.Side(vertex0, vertex1);
  7832. },
  7833. getCAG: function(sourcecag) {
  7834. var _this = this;
  7835. var newsides = sourcecag.sides.map(function(side) {
  7836. return _this.getSide(side);
  7837. })
  7838. // remove bad sides (mostly a user input issue)
  7839. .filter(function(side) {
  7840. return side.length() > 1e-5;
  7841. });
  7842. return CAG.fromSides(newsides);
  7843. }
  7844. };
  7845. //////////////////////////////////////
  7846. CSG.addTransformationMethodsToPrototype(CSG.prototype);
  7847. CSG.addTransformationMethodsToPrototype(CSG.Vector2D.prototype);
  7848. CSG.addTransformationMethodsToPrototype(CSG.Vector3D.prototype);
  7849. CSG.addTransformationMethodsToPrototype(CSG.Vertex.prototype);
  7850. CSG.addTransformationMethodsToPrototype(CSG.Plane.prototype);
  7851. CSG.addTransformationMethodsToPrototype(CSG.Polygon.prototype);
  7852. CSG.addTransformationMethodsToPrototype(CSG.Line3D.prototype);
  7853. CSG.addTransformationMethodsToPrototype(CSG.Connector.prototype);
  7854. CSG.addTransformationMethodsToPrototype(CSG.Path2D.prototype);
  7855. CSG.addTransformationMethodsToPrototype(CSG.Line2D.prototype);
  7856. CSG.addTransformationMethodsToPrototype(CAG.prototype);
  7857. CSG.addTransformationMethodsToPrototype(CAG.Side.prototype);
  7858. CSG.addTransformationMethodsToPrototype(CSG.OrthoNormalBasis.prototype);
  7859. CSG.addCenteringToPrototype(CSG.prototype, ['x', 'y', 'z']);
  7860. CSG.addCenteringToPrototype(CAG.prototype, ['x', 'y']);
  7861. /*
  7862. 2D polygons are now supported through the CAG class.
  7863. With many improvements (see documentation):
  7864. - shapes do no longer have to be convex
  7865. - union/intersect/subtract is supported
  7866. - expand / contract are supported
  7867. But we'll keep CSG.Polygon2D as a stub for backwards compatibility
  7868. */
  7869. CSG.Polygon2D = function(points) {
  7870. var cag = CAG.fromPoints(points);
  7871. this.sides = cag.sides;
  7872. };
  7873. CSG.Polygon2D.prototype = CAG.prototype;
  7874. //console.log('module', module)
  7875. module.CSG = CSG;
  7876. module.CAG = CAG;
  7877. })(this); //module to export to
  7878. // module.exports = {CSG,CAG}//({})(module)