-- Loads a sun + sky environment from the Sun and Reflection sections of an IBL file -- -- @description Loads a sun + sky environment from an IBL file -- @author Roeland Schoukens -- @shortcut -- @script-id load-sIBL -- @version 0.2 -- -- As an alternative to checking the version number, you can test if -- the features you're using are there: if not octane.NT_FILE then error("Octane version too old, this script requires version 2.11 or later.") end local inputs local environmentNode local textureNode local iblEnvironmentPower = 0 local iblSunDirection local defaultName = octane.apiinfo.getGraphInfo(octane.GT_SCRIPTED).defaultName local function setName(graph, name) -- if the graph still has the default name, set it to the name specified in the header if graph.name == defaultName or graph.name == "" then graph.name = name end defaultName = name end -- loads an IBL file and update our nodes with information from this file. function updateEnvironment(graph, file) -- magic number in Octane. local BORDER_MODE_CLAMP = 3 -- magic numbers used in sIBL files for the REFmap values local LON_LAT, CYLINDRICAL, ANGULAR, SCREEN = 1, 2, 3, 4 -- show error function showError(s) octane.gui.showDialog{ type = octane.gui.dialogType.BUTTON_DIALOG, text = s, title = "load sIBL" } end -- parse an INI value function parseval(v) -- nil if v == nil then return nil end v = v:match("^%s*(.-)%s*$") if v == "" then return nil end -- quoted string local str = v:match('"(.*)"') if str ~= nil then return str end -- color local r, g, b = v:match("([%d.]+),([%d.]+),([%d.]+)") if b ~= nil then return {tonumber(r), tonumber(g), tonumber(b)} end -- float local fl = tonumber(v) if fl ~= nil then return fl end -- return token as string return v end -- data we're loading -- section headers we read from sIBL files. For now we only support sun+environment, -- note that some files contain other lights. local ibl = { Header = {}, Reflection = {}, Sun = {}, } local section = nil -- parse the sIBL file (INI syntax) for line in io.lines(file) do local sectionTitle = line:match("%[(.+)%]") if sectionTitle ~= nil then section = ibl[sectionTitle] elseif section ~= nil then local k, v = line:match("%s*(%a+)%s+=%s+(.+)") if k ~= nil then section[k] = parseval(v) end end end -- we want a REF and SUN section if not ibl.Reflection.REFfile or not ibl.Sun.SUNu then showError("We can load only sIBL files with an environment + sun") return end -- helper function to put a node of some type in a pin, and optionally set a value function PN(node, pin, type, attrId, value) local n = octane.node.create { pinOwnerNode = node, pinOwnerId = pin, type = type } if attrId ~= nil and value ~= nil then n:setAttribute(attrId, value) end return n end -- We are not doing conversions between environment mapping systems... if ibl.Reflection.REFmap == LON_LAT then -- some values can be applied directly local texFile = octane.file.join(file, "..", ibl.Reflection.REFfile) textureNode.name = octane.file.getFileName(texFile) textureNode:setAttribute(octane.A_FILENAME, texFile) textureNode:setPinValue(octane.P_GAMMA, ibl.Reflection.REFgamma) iblEnvironmentPower = ibl.Reflection.REFmulti PN(textureNode, octane.P_TRANSFORM, octane.NT_TRANSFORM_VALUE, octane.A_TRANSLATION, {ibl.Reflection.REFu, ibl.Reflection.REFv, 0}) -- set texture mapping, taking north offset into account local projection = PN(textureNode, octane.P_PROJECTION, octane.NT_PROJ_SPHERICAL) -- calculate the sun direction ibl.Sun.SUNu = ibl.Sun.SUNu + ibl.Reflection.REFu ibl.Sun.SUNv = ibl.Sun.SUNv + ibl.Reflection.REFv local sinth = math.sin(ibl.Sun.SUNv * math.pi) local x = - math.sin(ibl.Sun.SUNu * 2 * math.pi) * sinth local y = math.cos(ibl.Sun.SUNv * math.pi) local z = math.cos(ibl.Sun.SUNu * 2 * math.pi) * sinth iblSunDirection = {x, y, z} PN(environmentNode, octane.P_SUN_DIR, octane.NT_FLOAT) else showError("We can only render environments with lon-lat mapping") end setName(graph, ibl.Header.Name) -- we are ignoring ibl.Sun.SUNmulti and ibl.Sun.SUNcolor, since Octane varies both the -- sun brightness and sun color depending on where it is in the sky. end local T = {} function T.onInit(self, graph) inputs = graph:setInputLinkers{ { type = octane.PT_STRING, label="file", defaultNodeType=octane.NT_FILE, filePattern="*.ibl" }, { type = octane.PT_FLOAT, label="sun power", fromNodeType=octane.NT_ENV_DAYLIGHT, fromPinId=octane.P_POWER }, { type = octane.PT_FLOAT, label="environment power", fromNodeType=octane.NT_ENV_DAYLIGHT, fromPinId=octane.P_POWER }, { type = octane.PT_FLOAT, label="sun size", fromNodeType=octane.NT_ENV_DAYLIGHT, fromPinId=octane.P_SUN_SIZE }, { type = octane.PT_TRANSFORM, label="transform", fromNodeType=octane.NT_PROJ_SPHERICAL, fromPinId=octane.P_TRANSFORM }, } local outputs = graph:setOutputLinkers{ { type = octane.PT_ENVIRONMENT, label="environment" } } environmentNode = octane.node.create{ type=octane.NT_ENV_DAYLIGHT, name="environment", graphOwner=graph } textureNode = octane.node.create{ type=octane.NT_TEX_IMAGE, graphOwner=graph } environmentNode:connectTo(octane.P_TEXTURE, textureNode) outputs[1]:connectTo(octane.P_INPUT, environmentNode) environmentNode:connectTo(octane.P_SUN_SIZE, inputs[4]) -- set icon, and release base64 blob self:setIcon(octane.image.fromBase64(self.iconData, octane.image.channels.COLOUR)) self.iconData = nil setName(graph, "sIBL environment") end function T.onEvaluate(self, graph) if self:inputWasChanged(inputs[1]) then local file = self:getInputValue(inputs[1]) if file ~= "" then updateEnvironment(graph, file) end end if iblSunDirection then local xform = self:getInputValue(inputs[5]) -- adapt the sun direction to the transform. This doesn't work with translations, -- solving this for translations is hard. local dir = octane.matrix.mulP(xform, iblSunDirection) dir = octane.vec.normalized(dir) environmentNode:setPinValue(octane.P_POWER, self:getInputValue(inputs[2])) environmentNode:setPinValue(octane.P_SUN_DIR, dir) textureNode:getInputNode(octane.P_PROJECTION):setPinValue(octane.P_TRANSFORM, xform) textureNode:setPinValue(octane.P_POWER, self:getInputValue(inputs[3]) * iblEnvironmentPower / self:getInputValue(inputs[2])) end return true end -- base64 encoded PNG file. If we put it in our table like this we can put this chunk at -- the bottom of our script. T.iconData = [[iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAkNJREFUOI2Nkb9LW1Ecxc/NDfnxHjg4FC uCdHhLBlsaxf4Jbq5m6h4kBbf+AaXgYsXB0VkQBMHNCCYIDnmWPkM1iNGhESSJj/pK3qXv3Xs6SNKipu2ZvsP5HM73+xV4WtbU1NT72d nZt+l0OtNqtWrb29sfAVSH+H8rmUy+293dJUne3d2xr1arxUKh8PWvcCKRKPm+z+PjY3NyckKllCHJOI6NMYYkubi4+GMYb+3s7NDzPB MEAbXWRmvNPhjHsdFakyRzudzqIzqXy30gSc/zBjBJ9no9djodhmFIrTW11mZjY4ODlftDPp8vttttTExMUAghtNY4ODhAvV6HUgqFQg FjY2OUUoqZmRkAcACcDwKklKkoimDbthBCYG9vD77vo1gsQkqJRqMBKaUAANu2AcAGgEQ/4Pr6ujw+Po5UKkUA6HQ6mJ+fRyJxb1FKgS SFELi8vASA84dneHl6ekqtNeM4NiRZqVQYhiFJ0nXdwUuXlpYewQCAubm5CkkaYwYH29/fJ0k2Gg2jlKLneQTwetgrsbCw0IzjmP0gkj w8PCRJlstllkol/0+/fBhQr9dX19bWwmQy+SaKonQQBAiCgFtbW5icnBRKqUw+n7+qVqtfAEAMrXKvLIBny8vLV47j4ObmBkIInp2d/V xZWck82eCBYgDfp6enX1iW9arb7YKkyGazScdxvtVqtc+JfwQAALrdbrHZbEJKCcuykMlksL6+7v5PAwCA67rR6Ohob2Rk5PnFxUV7c3 Pz6Pb29hMA/ALZznx4mDAKFgAAAABJRU5ErkJggg==]] return T