Skip to content

niNode⚓︎

Base class that represents the nodes of a scene graph. A node can have any number of child nodes.

This type inherits the following: niAVObject, niObjectNET, niObject

Example: Attaching a mesh directly to the actor's scene graph
--- This function will attach an equipment mesh directly to the
--- reference's scene graph. The skeleton (bone names) in the
--- equipment mesh should match the destination skeleton.
---@param mesh niNode
---@param reference tes3reference
local function attachMesh(mesh, reference)
    for shape in table.traverse(mesh.children) do
        if shape:isInstanceOfType(ni.type.NiTriShape) then
            ---@cast shape niTriShape
            local skin = shape.skinInstance
            local referenceRoot = reference.sceneNode
            ---@cast referenceRoot niNode

            if skin then
                skin.root = referenceRoot:getObjectByName(skin.root.name) --[[@as niNode]]
                for i, bone in ipairs(skin.bones) do
                    skin.bones[i] = referenceRoot:getObjectByName(bone.name)
                end
            end
            ---@diagnostic disable-next-line
            referenceRoot:getObjectByName("Bip01"):attachChild(shape)
        end
    end
end

local function onLoaded()
    local mesh = tes3.loadMesh("c\\c_m_helseth_robe.nif"):clone() --[[@as niNode]]
    attachMesh(mesh, tes3.player)
end
event.register(tes3.event.loaded, onLoaded)
Example: Adding new shapes to the scene graph

The most basic of the scene graph operations is attaching. Attaching a node to the elements of the scene graph will make it visible.

---@type niTriShape
local arrows

local function onLoaded()
    -- MWSE ships with a mesh which contains a few useful widgets.
    -- These can be used during debugging.
    local mesh = tes3.loadMesh("mwse\\widgets.nif") --[[@as niNode]]
    local widgets = {
        -- 3D coordinate axes
        arrows = mesh:getObjectByName("unitArrows") --[[@as niTriShape]],
        -- A common switch node that has three almost infinite lines
        -- along each coordinate exis
        axes = mesh:getObjectByName("axisLines") --[[@as niSwitchNode]],
        plane = mesh:getObjectByName("unitPlane") --[[@as niTriShape]],
        sphere = mesh:getObjectByName("unitSphere") --[[@as niTriShape]]
    }

    local root = tes3.worldController.vfxManager.worldVFXRoot
    ---@cast root niNode

    -- Objects in the scene graph share as much same properties as possible, which
    -- allows for some optimizations. Because of that, we need to clone our object
    -- before attaching it to the scene graph.
    arrows = widgets.arrows:clone() --[[@as niTriShape]]

    -- The base size of the arrows is 1 unit. Let's make them a bit bigger.
    arrows.scale = 30

    -- Attaching our arrows shape to a node in the scene graph will
    -- make it actually visible in-game.
    root:attachChild(arrows)

    -- No changes are applied until the update() method was called on the parent node.
    root:update()
end
event.register(tes3.event.loaded, onLoaded)

local function simulateCallback()
    -- Let's set the base position of the arrows in the in-game world to the player's
    -- eye position. Then we offset it in the direction of eye vector by 200 units.
    -- We get the arrows following the player's cursor at 200 units distance.
    arrows.translation = tes3.getPlayerEyePosition() + tes3.getPlayerEyeVector() * 200
    arrows:update()
end
event.register(tes3.event.simulate, simulateCallback)
Example: Orienting an object so that it's perpendicular to the surface.

In this example the unit arrows are placed perpendicular to the surface in the direction the player is looking at.

---@type niTriShape
local arrows

local function onLoaded()
    -- MWSE ships with a mesh which contains a few useful widgets.
    -- These can be used during debugging.
    local mesh = tes3.loadMesh("mwse\\widgets.nif") --[[@as niNode]]
    local widgets = {
        -- 3D coordinate axes
        arrows = mesh:getObjectByName("unitArrows") --[[@as niTriShape]],
        -- A common switch node that has three almost infinite lines
        -- along each coordinate exis
        axes = mesh:getObjectByName("axisLines") --[[@as niSwitchNode]],
        plane = mesh:getObjectByName("unitPlane") --[[@as niTriShape]],
        sphere = mesh:getObjectByName("unitSphere") --[[@as niTriShape]]
    }

    local root = tes3.worldController.vfxManager.worldVFXRoot
    ---@cast root niNode

    -- Objects in the scene graph share as much same properties as possible, which
    -- allows for some optimizations. Because of that, we need to clone our object
    -- before attaching it to the scene graph.
    arrows = widgets.arrows:clone() --[[@as niTriShape]]

    -- The base size of the arrows is 1 unit. Let's make them a bit bigger.
    arrows.scale = 100

    -- Attaching our arrows shape to a node in the scene graph will
    -- make it actually visible in-game.
    root:attachChild(arrows)

    -- No changes are applied until the update() method was called on the parent node.
    root:update()
end
event.register(tes3.event.loaded, onLoaded)


-- This is the up axis unit vector.
local UP = tes3vector3.new(0, 0, 1)

--- This function returns the difference in direction
--- of two vectors in Euler angles in radians.
---@param vec1 tes3vector3
---@param vec2 tes3vector3
---@return tes3vector3, boolean?
local function rotationDifference(vec1, vec2)
    vec1 = vec1:normalized()
    vec2 = vec2:normalized()

    local axis = vec1:cross(vec2)
    local norm = axis:length()
    if norm < 1e-5 then
        return tes3vector3.new(0, 0, 0)
    end

    local angle = math.asin(norm)
    if vec1:dot(vec2) < 0 then
        angle = math.pi - angle
    end

    axis:normalize()
    local m = tes3matrix33.new()
    m:toRotation(-angle, axis.x, axis.y, axis.z)
    return m:toEulerXYZ()
end


local function simulateCallback()
    local hit = tes3.rayTest({
        position = tes3.getPlayerEyePosition(),
        direction = tes3.getPlayerEyeVector(),
        returnNormal = true,
        returnSmoothNormal = true,
        ignore = { tes3.player }
    })
    if not hit then return end

    -- First, set the the arrows position
    arrows.translation = hit.intersection

    -- Now, let's get the rotation
    local rotation = rotationDifference(UP, hit.normal)
    local m = tes3matrix33.new()

    -- We need to convert our rotation to a rotation matrix
    -- since nodes of scene graph store their rotations in
    -- that form.
    m:fromEulerXYZ(rotation.x, rotation.y, rotation.z)
    arrows.rotation = m

    arrows:update()
end
event.register(tes3.event.simulate, simulateCallback)

Properties⚓︎

alphaProperty⚓︎

Convenient access to this object's alpha property. Setting this value to be nil will erase the property, while setting it to a valid alpha property will set (or replace) it.

Returns:


appCulled⚓︎

A flag indicating if this object is culled. When culled, it will not render, and raycasts ignore it.

Returns:

  • result (boolean)

children⚓︎

Read-only. The children of the node. Can have nil entries.

Returns:


controller⚓︎

Read-only. The first controller available on the object.

Returns:


effectList⚓︎

Read-only. The effect list of the node. Attached effects affect the node and all of its child subtree geometry.

Returns:


extraData⚓︎

Read-only. The first extra data available on the object.

Returns:


flags⚓︎

Flags, dependent on the specific object type.

Returns:

  • result (number)

fogProperty⚓︎

Convenient access to this object's fog property. Setting this value to be nil will erase the property, while setting it to a valid fog property will set (or replace) it.

Returns:


materialProperty⚓︎

Convenient access to this object's material property. Setting this value to be nil will erase the property, while setting it to a valid material property will set (or replace) it.

Returns:


name⚓︎

The human-facing name of the given object.

Returns:

  • result (string)

parent⚓︎

Read-only. The object's parent. It may not have one if it is not attached to the scene.

Returns:


properties⚓︎

Read-only. The list of properties attached to this niAVObject.

Returns:


refCount⚓︎

Read-only. The number of references that exist for this object. When this value reaches zero, the object will be deleted.

Returns:

  • result (number)

rotation⚓︎

The object's local rotation matrix.

Returns:


RTTI⚓︎

Read-only. The runtime type information for this object. This is an alias for the .runTimeTypeInformation property.

Returns:


runTimeTypeInformation⚓︎

Read-only. The runtime type information for this object.

Returns:


scale⚓︎

The object's local uniform scaling factor.

Returns:

  • result (number)

stencilProperty⚓︎

Convenient access to this object's stencil property. Setting this value to be nil will erase the property, while setting it to a valid stencil property will set (or replace) it.

Returns:


texturingProperty⚓︎

Convenient access to this object's texturing property. Setting this value to be nil will erase the property, while setting it to a valid texturing property will set (or replace) it.

Returns:


translation⚓︎

The object's local translation vector.

Returns:


velocity⚓︎

The object's local velocity.

Returns:


vertexColorProperty⚓︎

Convenient access to this object's vertex coloring property. Setting this value to be nil will erase the property, while setting it to a valid vertex coloring property will set (or replace) it.

Returns:


worldBoundOrigin⚓︎

The world coordinates of the object's bounds origin.

Returns:


worldBoundRadius⚓︎

The radius of the object's bounds.

Returns:

  • result (number)

worldTransform⚓︎

The object's transformations in the world space.

Returns:


zBufferProperty⚓︎

Convenient access to this object's z-buffer property. Setting this value to be nil will erase the property, while setting it to a valid z-buffer property will set (or replace) it.

Returns:


Methods⚓︎

addExtraData⚓︎

Appends an extra data to the object.

myObject:addExtraData(extraData)

Parameters:


attachChild⚓︎

Attaches the child to the children list of the node. Doesn't check to see if the object is already in the child list.

myObject:attachChild(child, useFirstAvailable)

Parameters:

  • child (niAVObject)
  • useFirstAvailable (boolean): Default: false. Use the first available space in the list. If false appends the child at the end of the list.

attachEffect⚓︎

Attaches a dynamic effect to the node. It will not attach the same effect twice.

myObject:attachEffect(effect)

Parameters:


attachProperty⚓︎

Attaches a property to this object, without checking to see if the property or another of its type is already on the list. Property lists must not have more than one property of a given class (i.e. no two niTexturingProperty objects) attached at once, or else undefined behavior will result.

myObject:attachProperty(property)

Parameters:


clearTransforms⚓︎

Resets the object's local transform.

myObject:clearTransforms()

clone⚓︎

Creates a copy of this object.

local result = myObject:clone()

Returns:


copyTransforms⚓︎

Update object's local transform by copying from another source.

myObject:copyTransforms(source)

Parameters:


createBoundingBox⚓︎

Calculates and creates a bounding box for the object. The existing bounding box, if any, will not be used, a fresh one will always be calculated.

local boundingBox = myObject:createBoundingBox()

Returns:


detachAllChildren⚓︎

Detaches all children from the children list of the node.

myObject:detachAllChildren()

detachAllEffects⚓︎

Detaches all dynamic effect from the effect list of the node.

myObject:detachAllEffects()

detachAllProperties⚓︎

Detaches all the properties on the object and returns them in the table.

local result = myObject:detachAllProperties()

Returns:


detachChild⚓︎

Detaches the child from the children list of the node. Returns the detached child.

local detachedChild = myObject:detachChild(child)

Parameters:

Returns:


detachChildAt⚓︎

Detaches the child at the specified index from the children list of the node. Returns the detached child.

local detachedChild = myObject:detachChildAt(index)

Parameters:

  • index (integer)

Returns:


detachEffect⚓︎

Detaches the given dynamic effect from the effect list of the node, if it was present. Has no effect if the effect wasn't attached to the node.

myObject:detachEffect(effect)

Parameters:


detachProperty⚓︎

Detaches and returns a property from the object which matches the given property type.

local result = myObject:detachProperty(type)

Parameters:

Returns:


getEffect⚓︎

Gets the effect of the given type.

local effect = myObject:getEffect(type)

Parameters:

Returns:


getGameReference⚓︎

Searches for an niExtraData on this object to see if it has one that holds a related reference.

local reference = myObject:getGameReference(searchParents)

Parameters:

  • searchParents (boolean): Default: false. If true, all parent objects (if applicable) are also searched.

Returns:


getObjectByName⚓︎

Searches this node and all child nodes recursively for a node with a name that matches the argument.

local result = myObject:getObjectByName(name)

Parameters:

  • name (string)

Returns:


getProperty⚓︎

Gets an attached property by property type.

local result = myObject:getProperty(type)

Parameters:

Returns:


getStringDataStartingWith⚓︎

Searches for an niExtraData on this object to see if it has niStringExtraData that has its string start with the provided value argument.

local extra = myObject:getStringDataStartingWith(value)

Parameters:

  • value (string): The first niStringExtraData starting with this value will be returned.

Returns:


getStringDataWith⚓︎

Searches for an niExtraData on this object to see if it has niStringExtraData that has the provided value argument in its string field.

local extra = myObject:getStringDataWith(value)

Parameters:

  • value (string): The first niStringExtraData with this word will be returned.

Returns:


hasStringDataStartingWith⚓︎

Searches for an niExtraData on this object to see if it has niStringExtraData that has its string start with the provided value argument. Returns true if the value was found.

local result = myObject:hasStringDataStartingWith(value)

Parameters:

  • value (string): The value to search for.

Returns:

  • result (boolean)

hasStringDataWith⚓︎

Searches for an niExtraData on this object to see if it has niStringExtraData that contains the provided value argument in its string field. Returns true if the value was found.

local result = myObject:hasStringDataWith(value)

Parameters:

  • value (string): The value to search for.

Returns:

  • result (boolean)

isAppCulled⚓︎

Recursively checks if either the object or any of its parents are appCulled.

local result = myObject:isAppCulled()

Returns:

  • result (boolean)

isFrustumCulled⚓︎

Checks if the object is frustum culled for the given camera.

local result = myObject:isFrustumCulled(camera)

Parameters:

Returns:

  • result (boolean)

isInstanceOfType⚓︎

Determines if the object is of a given type, or of a type derived from the given type.

local result = myObject:isInstanceOfType(type)

Parameters:

Returns:

  • result (boolean)

isOfType⚓︎

Determines if the object is of a given type.

local result = myObject:isOfType(type)

Parameters:

Returns:

  • result (boolean)

prependController⚓︎

Add a controller to the object as the first controller.

myObject:prependController(controller)

Parameters:


propagatePositionChange⚓︎

Alias for update() method. Updates the world transforms of this node and its children, which makes changes visible for rendering. Use after changing any local rotation, translation, scale, bounds or after attaching and detaching nodes.

Tip

It's best to "batch up" calls to this method. For example, when transform of an object its parent and grandparent are all changed during the same frame, it is much more efficient to call this method only on the grandparent object after all transforms have been changed. Also, consider calling this function as low as possible on a scene graph.

myObject:propagatePositionChange({ time = ..., controllers = ..., bounds = ... })

Parameters:

  • args (table): Optional.
    • time (number): Default: 0. This parameter is the time-slice for transformation and bounds updates
    • controllers (boolean): Default: false. Update object's controllers?
    • bounds (boolean): Default: true. Update object's bounds?

removeAllControllers⚓︎

Removes all controllers.

myObject:removeAllControllers()

removeAllExtraData⚓︎

Removes all extra data.

myObject:removeAllExtraData()

removeController⚓︎

Removes a controller from the object.

myObject:removeController(controller)

Parameters:


removeExtraData⚓︎

Removes a specific extra data from the object.

myObject:removeExtraData(extraData)

Parameters:


saveBinary⚓︎

Serializes the object, and writes it to the given file.

local success = myObject:saveBinary(path)

Parameters:

  • path (string): The path to write the file at, relative to the Morrowind installation folder.

Returns:

  • success (boolean): If true the object was successfully serialized.

setFlag⚓︎

Sets a given flag in the niObjectNET flag data. The specifics use of the flag is dependent on the real underlying type.

myObject:setFlag(state, index)

Parameters:

  • state (boolean)
  • index (number)

update⚓︎

Updates the world transforms of this node and its children, which makes changes visible for rendering. Use after changing any local rotation, translation, scale, bounds or after attaching and detaching nodes.

Update Efficiency

It's best to "batch up" calls to this method. For example, when transform of an object its parent and grandparent are all changed during the same frame, it is much more efficient to call this method only on the grandparent object after all transforms have been changed. Also, consider calling this function as low as possible on a scene graph.

myObject:update({ time = ..., controllers = ..., children = ... })

Parameters:

  • args (table): Optional.
    • time (number): Default: 0. This parameter is passed to controllers. Only needed if controllers are being updated.
    • controllers (boolean): Default: false. Update controllers before updating transforms.
    • children (boolean): Default: true. Recursively updates the children of this node.

updateEffects⚓︎

Update all attached effects. This method must be called at or above any object when dynamic effects are attached or detached from it

myObject:updateEffects()

updateProperties⚓︎

Update all attached properties.

myObject:updateProperties()

Functions⚓︎

new⚓︎

Creates a new, empty NiNode.

local node = niNode.new()

Returns: