How to use TextureStage.MNormal?

Hi, there is absolutly no full example in the web of texturestage, I tried to test it:

from math import pi, sin, cos

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import Sequence
from panda3d.core import *
from panda3d.physics import *
from panda3d.ode import *
from random import Random
from direct.particles.ParticleEffect import *
from direct.particles.Particles import *
from direct.particles.ForceGroup import *
from panda3d.bullet import *
from geom import *
from skybox import SkyBox
from direct.gui.OnscreenText import OnscreenText

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)
        self.render.setShaderAuto()

        ts = TextureStage("TextureStage")
        ts.setMode(TextureStage.MModulate)
        self.ts = ts

        tsNormal = TextureStage("TextureStageNormal")
        tsNormal.setMode(TextureStage.MNormal)
        self.tsNormal = tsNormal

        nodePath = NodePath(CubeGeom().loadNode())
        nodePath.reparentTo(self.render)
        texture = loader.loadTexture("planet.png")
        nodePath.setTexture(ts, texture)

        texture = loader.loadTexture("planet_normal.png")
        nodePath.setTexture(tsNormal, texture)

        light = DirectionalLight("Light")
        light.setColor(Vec4(1, 0, 0, 1))
        nodePath = self.render.attachNewNode(light)
        self.light = nodePath
        self.render.setLight(nodePath)

        self.taskMgr.add(self.update)
        self.accept("a", self.toggleLight)

    def update(self, task):
        self.light.setH(self.light.getH()+0.25)
        return task.cont

    def toggleLight(self):
        if self.render.hasLight(self.light):
            self.render.clearLight()
        else:
            self.render.setLight(self.light)

base = MyApp()
base.run()

But a black box appears when the light is on. Where is the error, or I am missing something ?
I didn’t understood the concept of “TextureStage” well, and i don’t really know how to use it with multiple objects to.

I have a second question, is that if we use in the same time vertices normal and texture normal, which one will be applies ? Will the model normal will be applies with a little modulation with the texture normal ?

Thanks

Hi, welcome to the forums!

Does your model have binormals and tangents generated for it?

Hey,
Ah ! No there isn’t. There is the code of Cube Geom which generate cube (by default cube with 2 triangle for each face), only normals.

from panda3d.core import *

class CubeGeom:
    # ORIGIN = (LEFT, FRONT, DOWN)
    UP = 0
    DOWN = 1
    LEFT = 2
    RIGHT = 3
    FRONT = 4
    BACK = 5


    def getNormal(self, x, y, z, f):
        if(face == self.UP):
            return 0, 0, 1
        elif(face == self.DOWN):
            return 0, 0, -1
        elif(face == self.LEFT):
            return 1, 0, 0
        elif(face == self.RIGHT):
            return -1, 0, 0
        elif(face == self.FRONT):
            return 0, 1, 0
        elif(face == self.BACK):
            return 0, -1, 0
        else:
            raise Exception("Invalid face")

    def __init__(self, detail = 2):
            if detail < 2:
                raise Exception("detail < 2")

            self.detail = detail
            self.max = detail - 1
            self.faces = 6
            self.count = detail * detail * self.faces
            self.init()
            self.vdata = self.loadData()
            self.prim = self.loadPrim()
            self.geom = self.loadGeom()

    def init(self):
        pass

    def getNormal(self, x, y, z, f):
        v = LVector3f(x, y, z)
        v.normalize()
        return v.x, v.y, v.z

    def loadData(self):
        detail = self.detail
        max = self.max
        faces = self.faces
        count = self.count
        vdata = GeomVertexData("Cube", GeomVertexFormat.get_v3n3t2(), Geom.UHStatic)
        vdata.setNumRows(count)
        vertex = GeomVertexWriter(vdata, "vertex")
        normal = GeomVertexWriter(vdata, "normal")
        texcoord = GeomVertexWriter(vdata, "texcoord")

        for f in range(faces):
            for x in range(detail):
                for y in range(detail):
                    rx, ry, rz = self.getCoords(x, y, f)
                    nx, ny, nz = self.getNormal(rx, ry, rz, f)
                    vertex.addData3(rx, ry, rz)
                    normal.addData3(nx, ny, nz)

                    # Same Texture for all faces
                    tx = x / max
                    ty = y / max
                    texcoord.addData2(tx, ty)

        return vdata

    def scale(self, x):
        max = self.max
        x = x / max # map to [0; 1]
        x *= 2 # map to [0; 2]
        x -= 1 # map to [-1; 1]
        return x

    def getCoords(self, x, y, face):
        max = self.max
        x, y, z = self.getCoordsInt(x, y, face)
        x = self.scale(x)
        y = self.scale(y)
        z = self.scale(z)
        return x, y, z

    def getCoordsInt(self, x, y, face):
        m = self.detail - 1
        if(face == self.UP):
            return x, y, m
        elif(face == self.DOWN):
            return x, y, 0
        elif(face == self.LEFT):
            return 0, x, y
        elif(face == self.RIGHT):
            return m, x, y
        elif(face == self.FRONT):
            return x, 0, y
        elif(face == self.BACK):
            return x, m, y
        else:
            raise Exception("Invalid face")

    def clockwise(self, face):
        return face == self.DOWN or face == self.LEFT or face == self.BACK

    def loadPrim(self):
        detail = self.detail
        faces = self.faces
        prim = GeomTriangles(Geom.UHStatic)

        for f in range(faces):
            for x in range(detail - 1):
                for y in range(detail - 1):
                    if(self.clockwise(f)):
                        prim.addVertex(self.index(x, y, f))
                        prim.addVertex(self.index(x, y + 1, f))
                        prim.addVertex(self.index(x + 1, y, f))

                        prim.addVertex(self.index(x + 1, y, f))
                        prim.addVertex(self.index(x, y + 1, f))
                        prim.addVertex(self.index(x + 1, y + 1, f))
                    else:
                        prim.addVertex(self.index(x, y, f))
                        prim.addVertex(self.index(x + 1, y, f))
                        prim.addVertex(self.index(x, y + 1, f))

                        prim.addVertex(self.index(x + 1, y, f))
                        prim.addVertex(self.index(x + 1, y + 1, f))
                        prim.addVertex(self.index(x, y + 1, f))

        return prim

    def index(self, x, y, f):
        return self.faceIndex(f) + x * self.detail + y

    def faceIndex(self, f):
        return self.detail * self.detail * f

    def loadGeom(self):
        vdata = self.vdata
        prim = self.prim
        geom = Geom(vdata)
        geom.addPrimitive(prim)
        return geom

    def loadNode(self):
        geom = self.geom
        node = GeomNode("Cube")
        node.addGeom(geom)
        return node

Thx I added the binormal and tangent with random ones and it works :wink:
Random among valid one, I took Vec3.forward() or Vec3.up() if equals to calculate the tangent with the cross product, and again the cross prodct to calculate the binormal.