11import { ConcurrentFill } from '/src/lib/floodfill/concurrent'
22import { Grid } from '/src/lib/grid'
3+ import { Direction } from '/src/lib/direction'
4+ import { Random } from '/src/lib/random'
35import { Point } from '/src/lib/geometry/point'
46
57import {
@@ -9,54 +11,62 @@ import {
911 DiffuseLandBasin ,
1012 Basin ,
1113 EMPTY ,
14+ WaterBasin ,
1215} from './type'
1316
1417
1518const FILL_CHANCE = .1 // chance of fill growing
1619const FILL_GROWTH = 4 // make fill basins grow bigger than others
20+ const MIDPOINT_RATE = .6
1721
1822
19- export function buildBasinGrid ( baseContext ) {
23+ export function buildMidpointGrid ( { rect, zoneRect} ) {
24+ const centerIndex = Math . floor ( zoneRect . width / 2 )
25+ // 60% around center point
26+ const offset = Math . floor ( centerIndex * MIDPOINT_RATE )
27+
28+ return Grid . fromRect ( rect , ( ) => {
29+ const x = centerIndex + Random . int ( - offset , offset )
30+ const y = centerIndex + Random . int ( - offset , offset )
31+ return zoneRect . pointToIndex ( [ x , y ] )
32+ } )
33+ }
34+
35+
36+ export function buildBasinModel ( baseContext ) {
2037 const { rect, world, typeMap} = baseContext
2138 // init basin id counter
2239 let basinId = 0
2340 // get surface border points and setup basin types and fill
24- const fillMap = new Map ( )
41+ const landFillMap = new Map ( )
42+ const waterFillMap = new Map ( )
2543 const surveyMap = new Map ( )
2644 const basinGrid = Grid . fromRect ( rect , point => {
2745 // reuse the process of basinGrid creation to determine river mouths
2846 // is this point an erosion path (possible river mouth)?
29- const survey = surveyNeighbors ( baseContext , point )
30- if ( world . surface . isLand ( point ) ) {
31- if ( world . surface . isBorder ( point ) ) {
32- // set type on init
47+ if ( world . surface . isBorder ( point ) ) {
48+ if ( world . surface . isLand ( point ) ) {
49+ const survey = surveyNeighbors ( baseContext , point )
50+ surveyMap . set ( basinId , survey )
3351 const type = buildBasinType ( world , survey )
3452 typeMap . set ( basinId , type . id )
35- surveyMap . set ( basinId , survey )
36- fillMap . set ( basinId , { origin : point } )
37- basinId ++
53+ landFillMap . set ( basinId , { origin : point } )
54+ } else {
55+ typeMap . set ( basinId , WaterBasin . id )
56+ waterFillMap . set ( basinId , { origin : point } )
3857 }
58+ basinId ++
3959 }
4060 return EMPTY
4161 } )
4262 const context = { ...baseContext , basinGrid, surveyMap}
43- // returns a grid storing basin ids
44- // ocean and lake/sea borders must grow at same time
45- // but lakes/seas are delayed to grow less
46- new BasinGridFill ( fillMap , context ) . complete ( )
63+ new LandBasinFill ( landFillMap , context ) . complete ( )
64+ new WaterBasinFill ( waterFillMap , context ) . complete ( )
4765 return basinGrid
4866}
4967
5068
51- class BasinGridFill extends ConcurrentFill {
52- onInitFill ( fill , fillPoint ) {
53- const { surveyMap} = fill . context
54- const survey = surveyMap . get ( fill . id )
55- // the basin opposite border is the parentPoint
56- // update erosion path
57- this . _fillBasin ( fill , fillPoint , survey . oppositeBorder )
58- }
59-
69+ class LandBasinFill extends ConcurrentFill {
6070 getChance ( fill ) { return FILL_CHANCE }
6171
6272 getGrowth ( fill ) {
@@ -65,31 +75,29 @@ class BasinGridFill extends ConcurrentFill {
6575 return basin . isEndorheic ? 1 : FILL_GROWTH
6676 }
6777
78+ onInitFill ( fill , fillPoint ) {
79+ const { surveyMap} = fill . context
80+ const survey = surveyMap . get ( fill . id )
81+ // the basin opposite border is the parentPoint
82+ // update erosion path
83+ this . _fillBasin ( fill , fillPoint , survey . oppositeBorder )
84+ }
85+
6886 onFill ( fill , fillPoint , parentPoint ) {
69- const { distanceGrid, erosionMaskGrid } = fill . context
87+ const { distanceGrid, directionBitmask } = fill . context
7088 // distance to source by point
71- const currentDistance = distanceGrid . wrapGet ( parentPoint )
89+ const currentDistance = distanceGrid . get ( parentPoint )
7290 distanceGrid . wrapSet ( fillPoint , currentDistance + 1 )
7391 // update parent point erosion path
7492 const upstream = Point . directionBetween ( parentPoint , fillPoint )
75- erosionMaskGrid . add ( parentPoint , upstream )
93+ directionBitmask . add ( parentPoint , upstream )
7694 this . _fillBasin ( fill , fillPoint , parentPoint )
7795 }
7896
7997 getNeighbors ( fill , parentPoint ) {
8098 return Point . around ( parentPoint )
8199 }
82100
83- _fillBasin ( fill , fillPoint , parentPoint ) {
84- const { erosionGrid, erosionMaskGrid, basinGrid} = fill . context
85- // basin id is the same as fill id
86- basinGrid . set ( fillPoint , fill . id )
87- // set erosion flow to parent
88- const direction = Point . directionBetween ( fillPoint , parentPoint )
89- erosionGrid . wrapSet ( fillPoint , direction . id )
90- erosionMaskGrid . add ( fillPoint , direction )
91- }
92-
93101 isEmpty ( fill , fillPoint , parentPoint ) {
94102 const { world, basinGrid, typeMap} = fill . context
95103 const basin = Basin . parse ( typeMap . get ( fill . id ) )
@@ -102,6 +110,16 @@ class BasinGridFill extends ConcurrentFill {
102110 // avoid fill if different types
103111 return target . isWater == parent . isWater
104112 }
113+
114+ _fillBasin ( fill , fillPoint , parentPoint ) {
115+ const { erosionGrid, directionBitmask, basinGrid} = fill . context
116+ // basin id is the same as fill id
117+ basinGrid . set ( fillPoint , fill . id )
118+ // set erosion flow to parent
119+ const direction = Point . directionBetween ( fillPoint , parentPoint )
120+ erosionGrid . wrapSet ( fillPoint , direction . id )
121+ directionBitmask . add ( fillPoint , direction )
122+ }
105123}
106124
107125
@@ -139,3 +157,52 @@ function buildBasinType(world, survey) {
139157 }
140158 return DiffuseLandBasin
141159}
160+
161+
162+ class WaterBasinFill extends ConcurrentFill {
163+ getChance ( fill ) { return FILL_CHANCE }
164+ getGrowth ( fill ) { return FILL_GROWTH }
165+
166+ getNeighbors ( fill , parentPoint ) {
167+ return Point . around ( parentPoint )
168+ }
169+
170+ onInitFill ( fill , fillPoint ) {
171+ const { world, erosionGrid, directionBitmask, basinGrid, typeMap } = fill . context
172+ // basin id is the same as fill id
173+ let riverDirection
174+ basinGrid . set ( fillPoint , fill . id )
175+ Point . adjacents ( fillPoint , ( sidePoint , direction ) => {
176+ if ( world . surface . isLand ( sidePoint ) ) {
177+ const sideId = basinGrid . get ( sidePoint )
178+ const sideType = Basin . parse ( typeMap . get ( sideId ) )
179+ if ( sideType && sideType . hasRivers ) {
180+ riverDirection = Point . directionBetween ( sidePoint , fillPoint )
181+ directionBitmask . add ( fillPoint , direction )
182+ }
183+ } else {
184+ directionBitmask . add ( fillPoint , direction )
185+ }
186+ } )
187+ const erosionDirection = riverDirection || Direction . randomCardinal ( )
188+ erosionGrid . set ( fillPoint , erosionDirection . id )
189+ }
190+
191+ isEmpty ( fill , point ) {
192+ const { world, basinGrid} = fill . context
193+ const basinIsEmpty = basinGrid . get ( point ) === EMPTY
194+ const isWater = world . surface . isWater ( point )
195+ return isWater && basinIsEmpty
196+ }
197+
198+ onFill ( fill , fillPoint , parentPoint ) {
199+ const { basinGrid, erosionGrid, directionBitmask} = fill . context
200+ // update parent point erosion path
201+ const upstream = Point . directionBetween ( fillPoint , parentPoint )
202+ const downstream = Point . directionBetween ( parentPoint , fillPoint )
203+ directionBitmask . add ( fillPoint , upstream )
204+ directionBitmask . add ( parentPoint , downstream )
205+ erosionGrid . set ( fillPoint , upstream . id )
206+ basinGrid . set ( fillPoint , fill . id )
207+ }
208+ }
0 commit comments