# Stuck. Attempt at creating an Infinite Treadmill Terrain with Away3D 5.0.9 (Code included)

**URL:** https://community.haxe.org/t/stuck-attempt-at-creating-an-infinite-treadmill-terrain-with-away3d-5-0-9-code-included/4772
**Category:** Haxe
**Created:** [February 15, 2026, 12:30am UTC](https://community.haxe.org/t/stuck-attempt-at-creating-an-infinite-treadmill-terrain-with-away3d-5-0-9-code-included/4772 "2026-02-15T00:30:42Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![jul](https://avatars.discourse-cdn.com/v2/letter/j/41988e/32.png) [@jul](https://community.haxe.org/u/jul)
#### Post date: [February 15, 2026, 12:30am UTC](https://community.haxe.org/t/stuck-attempt-at-creating-an-infinite-treadmill-terrain-with-away3d-5-0-9-code-included/4772/1 "2026-02-15T00:30:42Z")

</div>

So far, this is the best result I have been able to achieve…

 ![Screenshot from 2026-02-15 01-18-43](https://community.haxe.org/uploads/default/original/2X/9/927a6efec579181801373100b983487f500d96c3.jpeg)

(Haxe 4.3.6, Away3D 5.0.9, OpenFl 9.5.0, Lime 8.3.0, Hxcpp 4.3.2)

It’s supposed to be an heightmap based terrain, with volumes of course, but it’s all flat. Of course. But it scrolls and displays the texture. I can’t go any further. If someone wants to try and figure out how to achieve the final step, here’s the code.

Project.xml

```xml
<?xml version="1.0" encoding="utf-8"?>
<project>
	<meta title="InfiniteTerrain" package="com.example.terrain" version="1.0.0" company="User" />
	<app main="Main" file="InfiniteTerrain" path="bin" />
	
	<window width="1280" height="720" fps="60" hardware="true" allow-high-dpi="true" />
	
	<source path="src" />
	<haxelib name="openfl" />
	<haxelib name="away3d" />
	
	<assets path="assets" rename="assets" />
    
    <!-- Required for Vertex Texture Fetch (sampling textures in vertex shader) -->
    <window profile="baselineExtended" />
</project>

```

Main.hx

```haxe
package;

import away3d.containers.View3D;
import away3d.lights.DirectionalLight;
import away3d.materials.lightpickers.StaticLightPicker;
import away3d.textures.BitmapTexture;
import openfl.display.Sprite;
import openfl.events.Event;
import openfl.utils.Assets;
import openfl.display.BitmapData;
import openfl.geom.Vector3D;

class Main extends Sprite {
    private var _view:View3D;
    private var _terrain:TerrainManager;

    public function new() {
        super();
        if (Assets.exists("assets/heightMap.jpg")) {
            Assets.loadBitmapData("assets/heightMap.jpg").onComplete(init);
        } else {
            // Fallback: Create a procedural green grid if file is missing
            var bmd = new BitmapData(512, 512, false, 0);
                bmd.perlinNoise(72, 72, 4, 12, true, true, 7, true);
            init(bmd);
        }
    }

    private function init(textureData:BitmapData):Void {
        _view = new View3D();
        _view.backgroundColor = 0x87CEEB;
        addChild(_view);

        _view.camera.lens.far = 250000;
        _view.camera.y = 6000;
        
        var light = new DirectionalLight(-0.5, -1, 0.5);
        light.ambient = 0.4;
        _view.scene.addChild(light);
        
        var lightPicker = new StaticLightPicker([light]);
        var texture = new BitmapTexture(textureData);
        
        _terrain = new TerrainManager(texture, lightPicker);
        _view.scene.addChild(_terrain.mesh);
        
        addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }

    private function onEnterFrame(e:Event):Void {
        if (_view != null && _terrain != null) {
            // Continuous movement
            _view.camera.z += 100; 
            
            // Update terrain using camera position
            _terrain.update(_view.camera.position);
            _view.render();
        }
    }
}

```

TerrainManager.hx

```haxe
package;

import away3d.entities.Mesh;
import away3d.materials.TextureMaterial;
import away3d.materials.lightpickers.StaticLightPicker;
import away3d.primitives.PlaneGeometry;
import away3d.textures.BitmapTexture;
import away3d.bounds.BoundingSphere;
import openfl.geom.Vector3D;

class TerrainManager {
    public var mesh:Mesh;
    private var _method:VertexDisplacementMethod;
    private var _gridSize:Float = 150000;

    public function new(texture:BitmapTexture, lightPicker:StaticLightPicker) {
        // High density (200 segments) makes mountains look like your image
        var geometry = new PlaneGeometry(_gridSize, _gridSize, 200, 200);
        
        _method = new VertexDisplacementMethod(50000); 
        
        var material = new TextureMaterial(texture);
        material.lightPicker = lightPicker;
        material.repeat = true;
        material.ambient = 0.5;
        material.addMethod(_method);
        
        mesh = new Mesh(geometry, material);
        mesh.bounds = new BoundingSphere();
        // Massively tall bounding box to never cull the volume
        mesh.bounds.fromExtremes(-200000, -100000, -200000, 200000, 100000, 200000);
    }

    public function update(cameraPos:Vector3D):Void {
        // Fix the mesh to the camera center. NO SNAPPING = NO JITTER.
        mesh.x = cameraPos.x;
        mesh.z = cameraPos.z;

        // Tell the shader where the camera is so it can offset the math
        _method.setCameraPosition(cameraPos.x, cameraPos.z);
    }
}

```

VertexDisplacementMethod.hx

```haxe
package;

import away3d.materials.methods.EffectMethodBase;
import away3d.materials.methods.MethodVO;
import away3d.materials.compilation.ShaderRegisterCache;
import away3d.materials.compilation.ShaderRegisterElement;
import away3d.core.managers.Stage3DProxy;
import openfl.Vector;

class VertexDisplacementMethod extends EffectMethodBase {
    private var _camX:Float = 0;
    private var _camZ:Float = 0;
    private var _amplitude:Float;

    public function new(amplitude:Float = 40000) {
        super();
        _amplitude = amplitude;
    }

    public function setCameraPosition(x:Float, z:Float):Void {
        _camX = x;
        _camZ = z;
    }

    override public function initVO(vo:MethodVO):Void {
        vo.needsUV = true;
    }

    override public function activate(vo:MethodVO, stage3DProxy:Stage3DProxy):Void {
        var vIndex:Int = vo.vertexConstantsIndex;
        if (vIndex != -1) {
            var vData:Vector<Float> = vo.vertexData;
            // vcN: [CameraX, CameraZ, Amplitude, TextureScale]
            vData[vIndex] = _camX;
            vData[vIndex + 1] = _camZ;
            vData[vIndex + 2] = _amplitude;
            vData[vIndex + 3] = 0.000008; // Huge scale for large terrain

            // vcN+1: [Freq1, Freq2, Freq3, DetailPersistence]
            vData[vIndex + 4] = 0.0001; 
            vData[vIndex + 5] = 0.0003;
            vData[vIndex + 6] = 0.0009;
            vData[vIndex + 7] = 0.45;
        }
    }

    override public function getVertexCode(vo:MethodVO, regCache:ShaderRegisterCache):String {
        var dataReg:ShaderRegisterElement = regCache.getFreeVertexConstant();
        var mathReg:ShaderRegisterElement = regCache.getFreeVertexConstant();
        vo.vertexConstantsIndex = dataReg.index * 4;

        var world:ShaderRegisterElement = regCache.getFreeVertexVectorTemp();
        var hReg:ShaderRegisterElement = regCache.getFreeVertexVectorTemp();
        var tReg:ShaderRegisterElement = regCache.getFreeVertexVectorTemp();
        
        var code:String = "";
        
        // 1. CALCULATE WORLD POS: Camera + Local
        code += "add " + world + ".x, vt0.x, " + dataReg + ".x \n";
        code += "add " + world + ".z, vt0.z, " + dataReg + ".y \n";
        
        // 2. PROJECT UVs: Makes the texture stay pinned to the world
        code += "mul v0.xy, " + world + ".xz, " + dataReg + ".w \n";
        
        // 3. FRACTAL MOUNTAINS (Ridged Noise Math)
        // Octave 1: Large Hills
        code += "mul " + tReg + ".xz, " + world + ".xz, " + mathReg + ".x \n";
        code += "sin " + hReg + ".x, " + tReg + ".x \n";
        code += "cos " + hReg + ".y, " + tReg + ".z \n";
        code += "mul " + hReg + ".z, " + hReg + ".x, " + hReg + ".y \n";
        code += "abs " + hReg + ".z, " + hReg + ".z \n"; // Sharp peaks
        
        // Octave 2: Ridges
        code += "mul " + tReg + ".xz, " + world + ".xz, " + mathReg + ".y \n";
        code += "sin " + tReg + ".x, " + tReg + ".x \n";
        code += "cos " + tReg + ".y, " + tReg + ".z \n";
        code += "mul " + tReg + ".x, " + tReg + ".x, " + tReg + ".y \n";
        code += "abs " + tReg + ".x, " + tReg + ".x \n";
        code += "mul " + tReg + ".x, " + tReg + ".x, " + mathReg + ".w \n"; // Apply persistence
        code += "add " + hReg + ".z, " + hReg + ".z, " + tReg + ".x \n";
        
        // 4. APPLY DISPLACEMENT
        code += "mul " + hReg + ".z, " + hReg + ".z, " + dataReg + ".z \n";
        code += "add vt0.y, vt0.y, " + hReg + ".z \n"; 
        
        return code;
    }

    override public function getFragmentCode(vo:MethodVO, regCache:ShaderRegisterCache, targetReg:ShaderRegisterElement):String {
        return ""; 
    }
}

```

Good luck.

---

<div class="post-metadata">

### Author: ![kevin-kmetz](https://community.haxe.org/user_avatar/community.haxe.org/kevin-kmetz/32/2522_2.png) [@kevin-kmetz](https://community.haxe.org/u/kevin-kmetz)
#### Post date: [February 15, 2026, 1:24am UTC](https://community.haxe.org/t/stuck-attempt-at-creating-an-infinite-treadmill-terrain-with-away3d-5-0-9-code-included/4772/2 "2026-02-15T01:24:07Z")

</div>

This open pull request ( [Allow effect methods to modify vertex position. by player-03 · Pull Request #67 · openfl/away3d · GitHub](https://github.com/openfl/away3d/pull/67) ) from @player_03 seems to suggest that effect methods cannot alter vertices.

This is way above my head though, so I could be wrong. Curious to hear what others think.

---

<div class="post-metadata">

### Author: ![player\_03](https://community.haxe.org/user_avatar/community.haxe.org/player_03/32/821_2.png) [@player\_03](https://community.haxe.org/u/player_03)
#### Post date: [February 19, 2026, 7:40pm UTC](https://community.haxe.org/t/stuck-attempt-at-creating-an-infinite-treadmill-terrain-with-away3d-5-0-9-code-included/4772/3 "2026-02-19T19:40:42Z")

</div>

Yes, as it stands, effect methods can’t alter vertex positions. Here’s the order of operations:

1. [Designate a temporary value to be `localPosition`](https://github.com/openfl/away3d/blob/5a0b89f1a83d57b74d6cf4fa782d74c93434b510/away3d/materials/compilation/ShaderCompiler.hx#L313). (Aside: I’d suggest using `sharedRegisters.localPosition` instead of hard-coding `vt0`, just in case it ever changes. It seems unlikely given that Away3D hard-codes it in places too, but better safe than sorry.)
2. [Project `localPosition` and assign it to `op`](https://github.com/openfl/away3d/blob/5a0b89f1a83d57b74d6cf4fa782d74c93434b510/away3d/materials/compilation/ShaderCompiler.hx#L326). `vt0` is now only for reference, since `op` can (I think) only be set once.
3. [Run effect methods](https://github.com/openfl/away3d/blob/5a0b89f1a83d57b74d6cf4fa782d74c93434b510/away3d/materials/compilation/ShaderCompiler.hx#L327). This is where your code goes.

Give my PR a try, and if it helps, please leave a comment!
