I’m trying to procedurally generate terrains through python as taught in the following tutorial (Brazilian Portuguese):
Unfortunately the component simply doesn’t work right from the beginning. The video link starts at the time the problem occurs to me. Maybe because he is using an older version.
A sample of the code:
#This component fails to generate noise on the plane.
import bge
from collections import OrderedDict
from mathutils import Vector, noise
class Terrain(bge.types.KX_PythonComponent):
args = OrderedDict([])
def start(self, args):
mesh = self.object.meshes[0]
for index in range(mesh.getVertexArrayLength(0)):
vertex = mesh.getVertex(0, index)
pos = vertex.XYZ
vertex.z = noise.hetero_terrain( pos, 1.0, 200.0, 8, 1.0 ) * 20.0
def update(self):
pass
Now we are trying to improve position with worldTransform so more planes can be put together.
import bge, bpy
from collections import OrderedDict
from mathutils import Vector, noise
class Terrain(bge.types.KX_PythonComponent):
args = OrderedDict([])
def start(self, args):
mesh = bpy.data.objects[str(self.object.name)].data
for vertex in mesh.vertices:
position = self.object.worldTransform * vertex.co
position.z = noise.hetero_terrain(position * 0.02, 1.0, 200.0, 8, 1.0) * 20.0
def update(self):
pass
And Blender returns the following error:
Blender Game Engine Started
<class ‘Matrix’>
[ERROR] KX_PythonComponent[Terrain] - Failed to invoke the start callback.
Traceback (most recent call last):
File “D:\gaming\UPBGE\projects\terrain\terrain.py”, line 12, in start
position = self.object.worldTransform * vertex.co
TypeError: Element-wise multiplication: not supported between ‘Matrix’ and ‘Vector’ types
Blender Game Engine Finished
[Logic Nodes][NOTIFICATION] Building Logic Trees on Startup…
Debug: 1586, 851
rcti: : xmin 0, xmax 1585, ymin 100, ymax 950 (1585x850)
I couldn’t find the answer elsewhere. How can it be solved?
Good question. The Vector() statement I forgot to remove.
I used it for testing. Feel free to remove it.
As for the str(), it’s always good practice to use it when using attribute values rather then raw strings, as not doing so can sometimes break the string syntax. It doesn’t always break, but again, occasionally*. You can also use modulus strings and also f-strings to replace the str() if you’d rather not use it.
Since we replaced KX_MeshProxy with bpy, ‘Mesh’ object has no attribute ‘getVertex’.
import bge
from bpy import data
from collections import OrderedDict
from mathutils import Vector, noise
class Terrain(bge.types.KX_PythonComponent):
args = OrderedDict([])
def start(self, args):
mesh = data.objects[self.object.name].data
# Generates topography
for vertex in mesh.vertices:
pos = vertex.co
# Turns the position into global to allow for continuity of relief between planes.
pos = self.object.worldTransform @ pos
pos /= 50.0 # Scales up noise
height = noise.hetero_terrain(pos, 1.0, 200.0, 8, 1.0)
height *= 20.0
vertex.co.z = height
# Updates vertex normals (lighting)
for poly in mesh.polygons:
v1 = mesh.getVertex(0, poly.v1).XYZ - mesh.getVertex(0, poly.v2).XYZ
v2 = mesh.getVertex(0, poly.v3).XYZ - mesh.getVertex(0, poly.v2).XYZ
for vertex in poly.vertices:
vertex.normal = v2.cross(v1).normalized()
def update(self):
pass
Blender Game Engine Started
[ERROR] KX_PythonComponent[Terrain] - Failed to invoke the start callback.
Traceback (most recent call last):
File “D:\UPBGE\…\terrain.py”, line 26, in start
v1 = mesh.getVertex(0, poly.v1).XYZ - mesh.getVertex(0, poly.v2).XYZ
AttributeError: ‘Mesh’ object has no attribute ‘getVertex’
Blender Game Engine Finished
for poly in mesh.polygons:
v1 = mesh.getVertex(0, poly.v1).XYZ - mesh.getVertex(0, poly.v2).XYZ
v2 = mesh.getVertex(0, poly.v3).XYZ - mesh.getVertex(0, poly.v2).XYZ
I think this:
o = data.objects[self.object.name]
for vertex in o.data.vertices:
v1 = o.matrix_world @ o.data.vertices[0].co - o.matrix_world @ o.data.vertices[1].co
v2 = o.matrix_world @ o.data.vertices[2].co - o.matrix_world @ o.data.vertices[1].co
As for this:
for vertex in poly.vertices:
vertex.normal = v2.cross(v1).normalized()
It’s a bit out of my league. Sorry. The best I could come up with at the moment was the code below, but it doesn’t work and raises an error:
import bge, bpy, mathutils
from collections import OrderedDict
class Terrain(bge.types.KX_PythonComponent):
args = OrderedDict([])
def start(self, args):
o = bpy.data.objects[self.object.name]
o.data.use_auto_smooth = True
o.data.normals_split_custom_set([(0, 0, 0) for l in o.data.loops])
normals = []
for vertex in o.data.vertices:
pos = vertex.co
pos = self.object.worldTransform @ pos
pos /= 50.0
height = mathutils.noise.hetero_terrain(pos, 1.0, 200.0, 8, 1.0)
height *= 20.0
vertex.co.z = height
for poly in o.data.polygons:
for vertex in o.data.vertices:
v1 = o.matrix_world @ o.data.vertices[0].co - o.matrix_world @ o.data.vertices[1].co
v2 = o.matrix_world @ o.data.vertices[2].co - o.matrix_world @ o.data.vertices[1].co
normals.append(vertex.normal)
o.data.normals_split_custom_set_from_vertices(normals)
def update(self):
pass
Raised error:
RuntimeError: Error: Number of custom normals is not number of vertices (73984.000000 / 289)
Edit: I think because it’s required for normal changes to be done in edit-mode, but that’s not exactly possible at runtime I believe. I could be wrong.