• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Terrain + HeightGenerator

Status
Not open for further replies.
Level 24
Joined
Aug 1, 2013
Messages
4,657
Ok, this is a bit of a combination of two systems...


1, Terrain (which is basically a way of setting the terrain height using Terrain Deformations.)
2, HeightGenerator (which is a procedural terrain generator using the Terrain API.)

JASS:
struct Terrain
   
    readonly static constant real GRID = 128.
    readonly static constant integer X_SIZE = 8192 // Configure to map.
    readonly static constant integer Y_SIZE = 8192 // Configure to map.
    readonly static constant integer X_GRID = .X_SIZE / R2I(.GRID)
    readonly static constant integer Y_GRID = .Y_SIZE / R2I(.GRID)
    readonly static          integer MIN_X_GRID
    readonly static          integer MIN_Y_GRID
    private static constant location LOCATION = Location(0, 0)
   
    private static terraindeformation array terrainDeformations[4096] // [.X_GRID*.Y_GRID]
    private static real array originalHeight[4096] // [.X_GRID*.Y_GRID]
   
    private static method create takes nothing returns thistype
        return 0
    endmethod
   
    public static method getHeight takes real x, real y returns real
        call MoveLocation(.LOCATION, x, y)
        return GetLocationZ(.LOCATION)
    endmethod
   
    public static method reduceHeight takes real x, real y, real z returns nothing
        local integer i = R2I(((x/.GRID)-.MIN_X_GRID) * .Y_GRID + ((y/.GRID)-.MIN_Y_GRID))
        local real newZ
        if .terrainDeformations[i] != null then
            call TerrainDeformStop(.terrainDeformations[i], 0)
            set z = z - (.getHeight(x, y) - .originalHeight[i])
        else
            set .originalHeight[i] = .getHeight(x, y)
        endif
        set .terrainDeformations[i] = TerrainDeformCrater(x, y, .GRID, z, 0, false)
    endmethod
    public static method addHeight takes real x, real y, real z returns nothing
        call .reduceHeight(x, y, -z)
    endmethod
    public static method setHeight takes real x, real y, real z returns nothing
        call .reduceHeight(x, y, .getHeight(x, y) - z)
    endmethod
   
    public static method resetHeight takes real x, real y returns nothing
        local integer i = R2I(((x/.GRID)-.MIN_X_GRID) * .Y_GRID + ((y/.GRID)-.MIN_Y_GRID))
        call TerrainDeformStop(.terrainDeformations[i], 0)
        set .terrainDeformations[i] = null
    endmethod
   
    private static method onInit takes nothing returns nothing
        local rect world = GetWorldBounds()
        set .MIN_X_GRID = R2I(GetRectMinX(world) / .GRID)
        set .MIN_Y_GRID = R2I(GetRectMinY(world) / .GRID)
        call RemoveRect(world)
        set world = null
       
        call TerrainDeformStop(TerrainDeformCrater(0, 0, 0, 0, 0, false), 0)
    endmethod
   
endstruct

(This one uses Thread because it would otherwise reach the OP limit pretty easily.)
JASS:
struct HeightGenerator
   
    private static thistype looping = 0
    private static real ty = 0
    private static real tx = 0
   
    public integer seed
    public real minHeight
    public real maxHeight
    public real minX
    public real minY
    public real maxX
    public real maxY
    public real smoothFactor
   
    public static method create takes integer seed returns thistype
        local thistype this = .allocate()
       
        set .seed = seed
       
        return this
    endmethod
   
    private method getNoise takes real x, real y returns real
        call SetRandomSeed(R2I(x*49632 + y*325176) + .seed)
        return GetRandomReal(.minHeight, .maxHeight)
    endmethod
   
    private method getSmoothNoise takes real x, real y returns real
        local real corners = (.getNoise(x-1, y-1) + .getNoise(x-1, y+1) + .getNoise(x+1, y-1) + .getNoise(x+1, y+1)) / 16.
        local real sides = (.getNoise(x-1, y) + .getNoise(x+1, y) + .getNoise(x, y-1) + .getNoise(x, y+1)) / 8.
        local real center = .getNoise(x, y) / 4.
        return corners + sides + center
    endmethod
   
    private static method interpolate takes real a, real b, real blend returns real
        local real theta = blend * bj_PI
        local real r = (1 - Cos(theta)) * 0.5
        return a * (1 - r) + b * r
    endmethod
   
    private method getInterpolatedNoise takes real x, real y returns real
        local integer intX = R2I(x)
        local integer intY = R2I(y)
        local real fracX = x - intX
        local real fracY = y - intY
       
        local real v1 = .getSmoothNoise(intX, intY)
        local real v2 = .getSmoothNoise(intX+1, intY)
        local real v3 = .getSmoothNoise(intX, intY+1)
        local real v4 = .getSmoothNoise(intX+1, intY+1)
       
        local real i1 = .interpolate(v1, v2, fracX)
        local real i2 = .interpolate(v3, v4, fracX)
        return .interpolate(i1, i2, fracY)
    endmethod
   
    private method generateHeight takes real x, real y returns real
        return .getInterpolatedNoise((x / Terrain.GRID - Terrain.MIN_X_GRID) / .smoothFactor, (y / Terrain.GRID - Terrain.MIN_Y_GRID) / .smoothFactor)
    endmethod
   
    private static method generateTerrainLoop takes nothing returns nothing
        local thistype this = .looping
       
        loop
            exitwhen .ty >= .maxY
            loop
                exitwhen .tx >= .maxX
               
                if Thread.count == 100 then
                    call Thread.new(function thistype.generateTerrainLoop)
                    return
                endif
               
                call Terrain.setHeight(.tx, .ty, .generateHeight(.tx, .ty))
               
                set .tx = .tx + Terrain.GRID
            endloop
           
            set .tx = .minX
            set .ty = .ty + Terrain.GRID
        endloop
    endmethod
   
    public method generateTerrain takes nothing returns nothing
        set .looping = this
        set .ty = .minY
        set .tx = .minX
        call Thread.new(function thistype.generateTerrainLoop)
    endmethod
   
    private static method resetTerrainLoop takes nothing returns nothing
        local thistype this = .looping
       
        loop
            exitwhen .ty >= .maxY
            loop
                exitwhen .tx >= .maxX
               
                if Thread.count == 100 then
                    call Thread.new(function thistype.resetTerrainLoop)
                    return
                endif
               
                call Terrain.resetHeight(.tx, .ty)
               
                set .tx = .tx + Terrain.GRID
            endloop
           
            set .tx = .minX
            set .ty = .ty + Terrain.GRID
        endloop
    endmethod
   
    public method resetTerrain takes nothing returns nothing
        set .looping = this
        set .ty = .minY
        set .tx = .minX
        call Thread.new(function thistype.resetTerrainLoop)
    endmethod
   
endstruct

Here is a small test script to try it out: (which is also present in the test map)
JASS:
globals
    HeightGenerator hg
endglobals

function Test2 takes nothing returns nothing
    call hg.resetTerrain()
endfunction

function Test1 takes nothing returns nothing
    local rect world = GetWorldBounds()
    set hg = HeightGenerator.create(GetRandomInt(0, 1000))
   
    set hg.minHeight = -100
    set hg.maxHeight = 700
    set hg.minX = -2048 // GetRectMinX(world)
    set hg.minY = -2048 // GetRectMinY(world)
    set hg.maxX = 2048 // GetRectMaxX(world)
    set hg.maxY = 2048 // GetRectMaxY(world)
    set hg.smoothFactor = 4
    call hg.generateTerrain()
   
    call RemoveRect(world)
    set world = null
endfunction

//===========================================================================
function InitTrig_Test takes nothing returns nothing
    call TimerStart(CreateTimer(), 0, false, function Test1)
    //call TimerStart(CreateTimer(), 5, false, function Test2)
endfunction

There is one problem with this thing... which is that terrain deformations are pretty cpu intense.
Especially because there are probably going to be a few thousand on the map if you use it a lot.

I tested this on
- a 2,048x2,048 terrain, where there is no lag at all.
- a 4,096x4,096 terrain, where you can notice the lag... mostly by not having a smooth camera.
- a 8,192x8,192 terrain, where this lag can be really annoying.
(A 480x480 map is a 61,440x61,440 terrain, which is going to lag so hard that you want to uninstall Warcraf III.)


So for small areas, this would be nice, but apart from that... it is not really useful until there is a better method of changing the height of a point in the terrain.
 

Attachments

  • test.w3m
    21.6 KB · Views: 66
Level 22
Joined
Feb 6, 2014
Messages
2,466
As I've mentioned here, when the terrain deformation is fogged, it is not visible hence it is ugly. Example, your footman is taking a stroll in a fogged area, suddenly a wild terrain deformation appears right in front of him as he started to unfog the area.

In your test map, try disabling the "iseedeadpeople" trigger and you'll see what I mean.
 
Level 19
Joined
Jul 2, 2011
Messages
2,162
the solution is simple

deform the land but prevent the user from seeing the change until it's done

how you may ask?

I was considering the volcano from the fire lord guy.... but... that cloud buff used on buildings to disable them... also covers a large area

this should prevent lags.... but I'm probably absolutely wrong
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Deforming the land is done almost in an instant (because of how it works internally, it is similar to having it on a 0s timer).

The problem is that the terrain deformations are only visible in fog of war.
So if you have no units nearby, you wont see the terrain deformation, when your units walk to the terrain, it suddenly increases/decreases based on how much it got lowered/highened.
 
Status
Not open for further replies.
Top