|
local AppendVertex = pstr().AppendVertex |
|
local GetHeight = terrain.GetHeight |
|
local height_tile = const.HeightTileSize |
|
local InvalidZ = const.InvalidZ |
|
local KeepRefOneFrame = KeepRefOneFrame |
|
|
|
local SetCustomData |
|
local GetCustomData |
|
function OnMsg.Autorun() |
|
SetCustomData = ComponentCustomData.SetCustomData |
|
GetCustomData = ComponentCustomData.GetCustomData |
|
end |
|
|
|
DefineClass.CodeRenderableObject = |
|
{ |
|
__parents = { "Object", "ComponentAttach", "ComponentCustomData" }, |
|
entity = "", |
|
flags = { |
|
gofAlwaysRenderable = true, cfCodeRenderable = true, cofComponentInterpolation = true, cfConstructible = false, |
|
efWalkable = false, efCollision = false, efApplyToGrids = false, efSelectable = false, efShadow = false, efSunShadow = false |
|
}, |
|
depth_test = false, |
|
zwrite = true, |
|
} |
|
|
|
DefineClass.Text = |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__parents = { "CodeRenderableObject" }, |
|
text = false, |
|
text_style = false, |
|
hide_in_editor = true, |
|
} |
|
|
|
local TextFlag_DepthTest = 1 |
|
local TextFlag_Center = 2 |
|
|
|
function Text:SetColor1(c) SetCustomData(self, const.CRTextCCDIndexColorMain, c) end |
|
function Text:SetColor2(c) SetCustomData(self, const.CRTextCCDIndexColorShadow, c) end |
|
function Text:GetColor1(c) return GetCustomData(self, const.CRTextCCDIndexColorMain) end |
|
function Text:GetColor2(c) return GetCustomData(self, const.CRTextCCDIndexColorShadow) end |
|
|
|
function Text:SetColor(c) |
|
self:SetColor1(c) |
|
self:SetColor2(RGB(0,0,0)) |
|
end |
|
|
|
function Text:GetColor(c) |
|
return self:GetColor1() |
|
end |
|
|
|
function Text:SetDepthTest(depth_test) |
|
local flags = GetCustomData(self, const.CRTextCCDIndexFlags) |
|
if depth_test then |
|
SetCustomData(self, const.CRTextCCDIndexFlags, FlagSet(flags, TextFlag_DepthTest)) |
|
else |
|
SetCustomData(self, const.CRTextCCDIndexFlags, FlagClear(flags, TextFlag_DepthTest)) |
|
end |
|
end |
|
function Text:GetDepthTest() |
|
return IsFlagSet(GetCustomData(self, const.CRTextCCDIndexFlags), TextFlag_DepthTest) |
|
end |
|
|
|
function Text:SetCenter(c) |
|
local flags = GetCustomData(self, const.CRTextCCDIndexFlags) |
|
if c then |
|
SetCustomData(self, const.CRTextCCDIndexFlags, FlagSet(flags, TextFlag_Center)) |
|
else |
|
SetCustomData(self, const.CRTextCCDIndexFlags, FlagClear(flags, TextFlag_Center)) |
|
end |
|
end |
|
function Text:GetCenter() |
|
return IsFlagSet(GetCustomData(self, const.CRTextCCDIndexFlags), TextFlag_Center) |
|
end |
|
|
|
function Text:SetText(txt) |
|
KeepRefOneFrame(self.text) |
|
self.text = txt |
|
SetCustomData(self, const.CRTextCCDIndexText, self.text) |
|
end |
|
function Text:GetText() |
|
return self.text |
|
end |
|
|
|
function Text:SetFontId(id) SetCustomData(self, const.CRTextCCDIndexFont, id) end |
|
function Text:GetFontId() return GetCustomData(self, const.CRTextCCDIndexFont) end |
|
function Text:SetShadowOffset(so) SetCustomData(self, const.CRTextCCDIndexShadowOffset, so) end |
|
function Text:GetShadowOffset() return GetCustomData(self, const.CRTextCCDIndexShadowOffset) end |
|
|
|
function Text:SetTextStyle(style, scale) |
|
local style = TextStyles[style] |
|
if not style then |
|
assert(false, string.format("Invalid text style '%s'", style)) |
|
return |
|
end |
|
scale = scale or terminal.desktop.scale:y() |
|
local font, height, base_height = style:GetFontIdHeightBaseline(scale) |
|
self:SetFontId(font, height, base_height) |
|
self:SetColor(style.TextColor) |
|
self:SetShadowOffset(style.ShadowSize) |
|
self.text_style = style |
|
end |
|
|
|
function Text:SetOpacityInterpolation(v0, t0, v1, t1) |
|
|
|
|
|
v0 = v0 or 100 |
|
v1 = v1 or v0 |
|
t0 = t0 or 0 |
|
t1 = t1 or 0 |
|
SetCustomData(self, const.CRTextCCDIndexOpacity, EncodeBits(v0, 7, v1, 7, t0/10, 8, t1/10, 8)) |
|
end |
|
|
|
function Text:SetScaleInterpolation(v0, t0, v1, t1) |
|
|
|
|
|
v0 = v0 or 100 |
|
v1 = v1 or v0 |
|
t0 = t0 or 0 |
|
t1 = t1 or 0 |
|
SetCustomData(self, const.CRTextCCDIndexScale, EncodeBits(v0/4, 7, v1/4, 7, t0/10, 8, t1/10, 8)) |
|
end |
|
|
|
function Text:SetZOffsetInterpolation(v0, t0, v1, t1) |
|
|
|
|
|
v0 = v0 or 0 |
|
v1 = v1 or v0 |
|
t0 = t0 or 0 |
|
t1 = t1 or 0 |
|
SetCustomData(self, const.CRTextCCDIndexZOffset, EncodeBits(v0/50, 7, v1/50, 7, t0/10, 8, t1/10, 8)) |
|
end |
|
|
|
function Text:Init() |
|
self:SetTextStyle(self.text_style or "EditorText") |
|
end |
|
|
|
function Text:Done() |
|
KeepRefOneFrame(self.text) |
|
self.text = nil |
|
end |
|
|
|
function Text:SetCustomData(idx, data) |
|
assert(idx ~= const.CRTextCCDIndexText, "Use SetText instead") |
|
return SetCustomData(self, idx, data) |
|
end |
|
|
|
DefineClass.TextEditor = { |
|
__parents = {"Text", "EditorVisibleObject"}, |
|
} |
|
|
|
function PlaceText(text, pos, color, editor_visibile_only) |
|
local obj = PlaceObject(editor_visibile_only and "TextEditor" or "Text") |
|
if pos then |
|
obj:SetPos(pos) |
|
end |
|
obj:SetText(text) |
|
if color then |
|
obj:SetColor(color) |
|
end |
|
return obj |
|
end |
|
|
|
function RemoveAllTexts() |
|
MapDelete("map", "Text") |
|
end |
|
|
|
local function GetMeshFlags() |
|
local flags = {} |
|
for name, value in pairs(const) do |
|
if string.starts_with(name, "mf") then |
|
flags[value] = name |
|
end |
|
end |
|
return flags |
|
end |
|
|
|
DefineClass.MeshParamSet = { |
|
__parents = { "PropertyObject" }, |
|
properties = { |
|
|
|
}, |
|
uniforms = false, |
|
uniforms_size = 0, |
|
} |
|
|
|
local uniform_sizes = { |
|
integer = 4, |
|
float = 4, |
|
color = 4, |
|
point2 = 8, |
|
point3 = 12, |
|
} |
|
|
|
function GetUniformMeta(properties) |
|
local uniforms = {} |
|
local offset = 0 |
|
for _, prop in ipairs(properties) do |
|
local uniform_type = prop.uniform |
|
if uniform_type then |
|
if type(uniform_type) ~= "string" then |
|
if prop.editor == "number" then |
|
uniform_type = prop.scale and prop.scale ~= 1 and "float" or "integer" |
|
elseif prop.editor == "point" then |
|
uniform_type = "point3" |
|
else |
|
uniform_type = prop.editor |
|
end |
|
end |
|
local size = uniform_sizes[uniform_type] |
|
if not size then |
|
assert(false, "Unknown uniform type.") |
|
end |
|
local space = 16 - (offset % 16) |
|
if space < size then |
|
table.insert(uniforms, {id = false, type = "padding", offset = offset, size = space}) |
|
offset = offset + space |
|
end |
|
table.insert(uniforms, {id = prop.id, type = uniform_type, offset = offset, size = size, scale = prop.scale}) |
|
offset = offset + size |
|
end |
|
end |
|
return uniforms, offset |
|
end |
|
|
|
function OnMsg.ClassesPostprocess() |
|
ClassDescendantsList("MeshParamSet", function(name, def) |
|
def.uniforms, def.uniforms_size = GetUniformMeta(def:GetProperties()) |
|
end) |
|
end |
|
|
|
function MeshParamSet:WriteBuffer(param_pstr, offset, getter) |
|
if not offset then |
|
offset = 0 |
|
end |
|
if not getter then |
|
getter = self.GetProperty |
|
end |
|
param_pstr = param_pstr or pstr("", self.uniforms_size) |
|
param_pstr:resize(offset) |
|
for _, prop in ipairs(self.uniforms) do |
|
local value |
|
if prop.type == "padding" then |
|
value = prop.size |
|
else |
|
value = getter(self, prop.id) |
|
end |
|
|
|
param_pstr:AppendUniform(prop.type, value, prop.scale) |
|
end |
|
return param_pstr |
|
end |
|
|
|
function MeshParamSet:ComposeBuffer(param_pstr, getter) |
|
return self:WriteBuffer(param_pstr, 0, getter) |
|
end |
|
|
|
DefineClass.Mesh = { |
|
__parents = { "CodeRenderableObject" }, |
|
|
|
properties = { |
|
{ id = "vertices_len", read_only = true, dont_save = true, editor = "number", default = 0,}, |
|
{ id = "CRMaterial", editor = "nested_obj", base_class = "CRMaterial", default = false, }, |
|
{ id = "MeshFlags", editor = "flags", default = 0, items = GetMeshFlags }, |
|
{ id = "DepthTest", editor = "bool", default = false, read_only = function(s) return not s.shader or s.shader.depth_test ~= "runtime" end, }, |
|
{ id = "ShaderName", editor = "choice", default = "default_mesh", items = function() return table.keys2(ProceduralMeshShaders, "sorted") end}, |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vertices_pstr = false, |
|
uniforms_pstr = false, |
|
shader = false, |
|
textstyle_id = false, |
|
} |
|
|
|
function Mesh:GedTreeViewFormat() |
|
return string.format("%s (%s)", self.class, self.CRMaterial and self.CRMaterial.id or self:GetShaderName()) |
|
end |
|
|
|
function Mesh:Getvertices_len() |
|
return self.vertices_pstr and #self.vertices_pstr or 0 |
|
end |
|
|
|
function Mesh:GetShaderName() |
|
return self.shader and self.shader.name or "" |
|
end |
|
|
|
function Mesh:SetShaderName(value) |
|
self:SetShader(ProceduralMeshShaders[value]) |
|
end |
|
|
|
function Mesh:Init() |
|
self:SetShader(ProceduralMeshShaders.default_mesh) |
|
end |
|
|
|
function Mesh:SetMesh(vpstr) |
|
KeepRefOneFrame(self.vertices_pstr) |
|
local vertices_pstr = #(vpstr or "") > 0 and vpstr or nil |
|
self.vertices_pstr = vertices_pstr |
|
SetCustomData(self, const.CRMeshCCDIndexGeometry, vertices_pstr) |
|
end |
|
|
|
function Mesh:SetUniformSet(uniform_set) |
|
self:SetUniformsPstr(uniform_set:ComposeBuffer()) |
|
end |
|
|
|
function Mesh:SetUniformsPstr(uniforms_pstr) |
|
KeepRefOneFrame(self.uniforms_pstr) |
|
self.uniforms_pstr = uniforms_pstr |
|
SetCustomData(self, const.CRMeshCCDIndexUniforms, uniforms_pstr) |
|
end |
|
|
|
function Mesh:SetUniformsList(uniforms, isDouble) |
|
KeepRefOneFrame(self.uniforms_pstr) |
|
local count = Max(8, #uniforms) |
|
local uniforms_pstr = pstr("", count * 4) |
|
self.uniforms_pstr = uniforms_pstr |
|
for i = 1, count do |
|
if isDouble then |
|
uniforms_pstr:AppendUniform("double", uniforms[i] or 0) |
|
else |
|
uniforms_pstr:AppendUniform("float", uniforms[i] or 0, 1000) |
|
end |
|
end |
|
SetCustomData(self, const.CRMeshCCDIndexUniforms, uniforms_pstr) |
|
end |
|
|
|
function Mesh:SetUniforms(...) |
|
return self:SetUniformsList{...} |
|
end |
|
|
|
function Mesh:SetDoubleUniforms(...) |
|
return self:SetUniformsList({...}, true) |
|
end |
|
|
|
function Mesh:SetShader(shader, depth_test) |
|
assert(shader.shaderid) |
|
assert(shader.defines) |
|
assert(shader.ref_id > 0) |
|
if depth_test == nil then |
|
if shader.depth_test == "always" then |
|
depth_test = true |
|
elseif shader.depth_test == "never" then |
|
depth_test = false |
|
else |
|
depth_test = self:GetDepthTest() |
|
end |
|
end |
|
local depth_test_int = 0 |
|
if depth_test then |
|
depth_test_int = 1 |
|
assert(shader.depth_test == "runtime" or shader.depth_test == "always", "Tried to enable depth test for shader with depth_test = never") |
|
else |
|
depth_test_int = 0 |
|
assert(shader.depth_test == "runtime" or shader.depth_test == "never", "Tried to disable depth test for shader with depth_test = always") |
|
end |
|
SetCustomData(self, const.CRMeshCCDIndexPipeline, shader.ref_id | (depth_test_int << 31)) |
|
self.shader = shader |
|
end |
|
|
|
function Mesh:SetDepthTest(depth_test) |
|
assert(self.shader) |
|
self:SetShader(self.shader, depth_test) |
|
end |
|
|
|
function Mesh:SetCRMaterial(material) |
|
if type(material) == "string" then |
|
local new_material = CRMaterial:GetById(material, true) |
|
assert(new_material, "CRMaterial not found.") |
|
material = new_material |
|
end |
|
self.CRMaterial = material |
|
local depth_test = material.depth_test |
|
if depth_test == "default" then depth_test = nil end |
|
|
|
CodeRenderableLockCCD(self) |
|
self:SetShader(material:GetShader(), depth_test) |
|
self:SetUniformsPstr(material:GetDataPstr()) |
|
CodeRenderableUnlockCCD(self) |
|
end |
|
|
|
function Mesh:GetCRMaterial() |
|
return self.CRMaterial |
|
end |
|
|
|
if FirstLoad then |
|
MeshTextureRefCount = {} |
|
end |
|
|
|
local function ModifyMeshTextureRefCount(id, change) |
|
if id == 0 then return end |
|
local old = MeshTextureRefCount[id] or 0 |
|
local new = old + change |
|
if new == 0 then |
|
MeshTextureRefCount[id] = nil |
|
ProceduralMeshReleaseResource(id) |
|
else |
|
MeshTextureRefCount[id] = new |
|
end |
|
end |
|
|
|
function Mesh:SetTexture(idx, resource_id) |
|
assert(idx >= 0 and idx <= 1) |
|
if self:GetTexture(idx) == resource_id then return end |
|
ModifyMeshTextureRefCount(self:GetTexture(idx), -1) |
|
SetCustomData(self, const.CRMeshCCDIndexTexture0 + idx, resource_id or 0) |
|
ModifyMeshTextureRefCount(resource_id, 1) |
|
end |
|
|
|
function Mesh:GetTexture(idx) |
|
assert(idx >= 0 and idx <= 1) |
|
return GetCustomData(self, const.CRMeshCCDIndexTexture0 + idx) or 0 |
|
end |
|
|
|
function Mesh:Done() |
|
KeepRefOneFrame(self.vertices_pstr) |
|
KeepRefOneFrame(self.uniforms_pstr) |
|
self.vertices_pstr = nil |
|
self.uniforms_pstr = nil |
|
self:SetTexture(0, 0) |
|
self:SetTexture(1, 0) |
|
end |
|
|
|
function OnMsg.DoneMap() |
|
for key, value in pairs(MeshTextureRefCount) do |
|
ProceduralMeshReleaseResource(key) |
|
end |
|
MeshTextureRefCount = {} |
|
end |
|
|
|
function Mesh:SetCustomData(idx, data) |
|
assert(idx > const.CRMeshCCDIndexGeometry, "Use SetMesh instead!") |
|
return SetCustomData(self, idx, data) |
|
end |
|
function Mesh:GetDepthTest() return (GetCustomData(self, const.CRMeshCCDIndexPipeline) >> 31) == 1 end |
|
function Mesh:SetMeshFlags(flags) SetCustomData(self, const.CRMeshCCDIndexMeshFlags, flags) end |
|
function Mesh:GetMeshFlags() return GetCustomData(self, const.CRMeshCCDIndexMeshFlags) end |
|
function Mesh:AddMeshFlags(flags) self:SetMeshFlags(flags | self:GetMeshFlags()) end |
|
function Mesh:ClearMeshFlags(flags) self:SetMeshFlags(~flags & self:GetMeshFlags()) end |
|
|
|
function Mesh.ColorFromTextStyle(id) |
|
assert(TextStyles[id]) |
|
return TextStyles[id].TextColor |
|
end |
|
|
|
function AppendCircleVertices(vpstr, center, radius, color, strip) |
|
local HSeg = 32 |
|
vpstr = vpstr or pstr("", 1024) |
|
color = color or RGB(254, 127, 156) |
|
center = center or point30 |
|
local x0, y0, z0 |
|
for i = 0, HSeg do |
|
local x, y, z = RotateRadius(radius, MulDivRound(360 * 60, i, HSeg), center, true) |
|
AppendVertex(vpstr, x, y, z, color) |
|
if not strip then |
|
if i ~= 0 then |
|
AppendVertex(vpstr, x, y, z, color) |
|
if i == HSeg then |
|
AppendVertex(vpstr, x0, y0, z0) |
|
end |
|
else |
|
x0, y0, z0 = x, y, z |
|
end |
|
end |
|
end |
|
|
|
return vpstr |
|
end |
|
|
|
function AppendTileVertices(vstr, x, y, z, tile_size, color, offset_z, get_height) |
|
offset_z = offset_z or 0 |
|
z = z or InvalidZ |
|
local d = tile_size / 2 |
|
local x1, y1, z1 = x - d, y - d |
|
local x2, y2, z2 = x + d, y - d |
|
local x3, y3, z3 = x - d, y + d |
|
local x4, y4, z4 = x + d, y + d |
|
get_height = get_height or GetHeight |
|
if z ~= InvalidZ and z ~= get_height(x, y) then |
|
z = z + offset_z |
|
z1, z2, z3, z4 = z, z, z, z |
|
else |
|
z1 = get_height(x1, y1) + offset_z |
|
z2 = get_height(x2, y2) + offset_z |
|
z3 = get_height(x3, y3) + offset_z |
|
z4 = get_height(x4, y4) + offset_z |
|
end |
|
AppendVertex(vstr, x1, y1, z1, color) |
|
AppendVertex(vstr, x2, y2, z2, color) |
|
AppendVertex(vstr, x3, y3, z3, color) |
|
AppendVertex(vstr, x4, y4, z4, color) |
|
AppendVertex(vstr, x2, y2, z2, color) |
|
AppendVertex(vstr, x3, y3, z3, color) |
|
end |
|
|
|
function GetSizePstrTile() |
|
return 6 * const.pstrVertexSize |
|
end |
|
|
|
function AppendTorusVertices(vpstr, radius1, radius2, axis, color, normal) |
|
local HSeg = 32 |
|
local VSeg = 10 |
|
vpstr = vpstr or pstr("", 1024) |
|
local rad1 = Rotate(axis, 90 * 60) |
|
rad1 = Cross(axis, rad1) |
|
rad1 = Normalize(rad1) |
|
rad1 = MulDivRound(rad1, radius1, 4096) |
|
for i = 1, HSeg do |
|
local localCenter1 = RotateAxis(rad1, axis, MulDivRound(360 * 60, i, HSeg)) |
|
local localCenter2 = RotateAxis(rad1, axis, MulDivRound(360 * 60, i - 1, HSeg)) |
|
local lastUpperPt, lastPt |
|
if not normal or not IsPointInFrontOfPlane(point(0, 0, 0), normal, (localCenter1 + localCenter2) / 2) then |
|
for j = 0, VSeg do |
|
local rad2 = MulDivRound(localCenter1, radius2, radius1) |
|
local localAxis = Cross(rad2, axis) |
|
local pt = RotateAxis(rad2, localAxis, MulDivRound(360 * 60, j, VSeg)) |
|
pt = localCenter1 + pt |
|
rad2 = MulDivRound(localCenter2, radius2, radius1) |
|
localAxis = Cross(rad2, axis) |
|
local upperPt = RotateAxis(rad2, localAxis, MulDivRound(360 * 60, j, VSeg)) |
|
upperPt = localCenter2 + upperPt |
|
if j ~= 0 then |
|
AppendVertex(vpstr, pt, color) |
|
AppendVertex(vpstr, lastPt) |
|
AppendVertex(vpstr, upperPt) |
|
AppendVertex(vpstr, upperPt, color) |
|
AppendVertex(vpstr, lastUpperPt) |
|
AppendVertex(vpstr, lastPt) |
|
end |
|
lastPt = pt |
|
lastUpperPt = upperPt |
|
end |
|
end |
|
end |
|
|
|
return vpstr |
|
end |
|
|
|
function AppendConeVertices(vpstr, center, displacement, radius1, radius2, axis, angle, color, offset) |
|
local HSeg = 10 |
|
vpstr = vpstr or pstr("", 1024) |
|
center = center or point(0, 0, 0) |
|
displacement = displacement or point(0, 0, 30 * guim) |
|
axis = axis or axis_z |
|
angle = angle or 0 |
|
offset = offset or point(0, 0, 0) |
|
color = color or RGB(254, 127, 156) |
|
local lastPt, lastUpperPt |
|
for i = 0, HSeg do |
|
local rad = point(radius1, 0, 0) |
|
local pt = center + Rotate(rad, MulDivRound(360 * 60, i, HSeg)) |
|
local upperRad = point(radius2, 0, 0) |
|
local upperPt = center + displacement + Rotate(upperRad, MulDivRound(360 * 60, i, HSeg)) |
|
pt = RotateAxis(pt, axis, angle * 60) + offset |
|
upperPt = RotateAxis(upperPt, axis, angle * 60) + offset |
|
if i ~= 0 then |
|
AppendVertex(vpstr, pt, color) |
|
AppendVertex(vpstr, lastPt) |
|
AppendVertex(vpstr, upperPt) |
|
if radius2 ~= 0 then |
|
AppendVertex(vpstr, upperPt, color) |
|
AppendVertex(vpstr, lastUpperPt) |
|
AppendVertex(vpstr, lastPt) |
|
end |
|
end |
|
lastPt = pt |
|
lastUpperPt = upperPt |
|
end |
|
|
|
return vpstr |
|
end |
|
|
|
DefineClass.Polyline = |
|
{ |
|
__parents = { "Mesh" }, |
|
} |
|
|
|
function Polyline:Init() |
|
self:SetMeshFlags(const.mfWorldSpace) |
|
self:SetShader(ProceduralMeshShaders.default_polyline) |
|
end |
|
|
|
DefineClass.Vector = { |
|
__parents = {"Polyline"}, |
|
} |
|
|
|
function Vector:Set (a, b, col) |
|
col = col or RGB(255, 255, 255) |
|
a = ValidateZ(a) |
|
b = ValidateZ(b) |
|
self:SetPos(a) |
|
|
|
local vpstr = pstr("", 1024) |
|
|
|
AppendVertex(vpstr, a, col) |
|
AppendVertex(vpstr, b) |
|
|
|
local ab = b - a |
|
local cb = (ab * 5) / 100 |
|
local f = cb:Len() / 4 |
|
local c = b - cb |
|
|
|
local n = 4 |
|
local ps = GetRadialPoints (n, c, cb, f) |
|
for i = 1 , n/2 do |
|
AppendVertex(vpstr, ps[i]) |
|
AppendVertex(vpstr, ps[i + n/2]) |
|
AppendVertex(vpstr, b) |
|
end |
|
self:SetMesh(vpstr) |
|
end |
|
|
|
function Vector:GetA() |
|
return self:GetPos() |
|
end |
|
|
|
function ShowVector(vector, origin, color, time) |
|
local v = PlaceObject("Vector") |
|
origin = origin:z() and origin or point(origin:x(), origin:y(), GetWalkableZ(origin)) |
|
vector = vector:z() and vector or point(vector:x(), vector:y(), 0) |
|
v:Set(origin, origin + vector, color) |
|
if time then |
|
CreateGameTimeThread(function() |
|
Sleep(time) |
|
DoneObject(v) |
|
end) |
|
end |
|
|
|
return v |
|
end |
|
|
|
DefineClass.Segment = { |
|
__parents = {"Polyline"}, |
|
} |
|
|
|
function Segment:Init() |
|
self:SetDepthTest(false) |
|
end |
|
|
|
function Segment:Set (a, b, col) |
|
col = col or RGB(255, 255, 255) |
|
a = ValidateZ(a) |
|
b = ValidateZ(b) |
|
self:SetPos(a) |
|
local vpstr = pstr("", 1024) |
|
AppendVertex(vpstr, a, col) |
|
AppendVertex(vpstr, b) |
|
self:SetMesh(vpstr) |
|
end |
|
|
|
|
|
function OnMsg.PersistLoad(_dummy_) |
|
MapForEach(true, "Text", function(obj) |
|
SetCustomData(obj, const.CRTextCCDIndexText, obj.text or 0) |
|
end) |
|
MapForEach(true, "Mesh", function(obj) |
|
CodeRenderableLockCCD(obj) |
|
SetCustomData(obj, const.CRMeshCCDIndexGeometry, obj.vertices_pstr or 0) |
|
SetCustomData(obj, const.CRMeshCCDIndexUniforms, obj.uniforms_pstr or 0) |
|
CodeRenderableUnlockCCD(obj) |
|
end) |
|
end |
|
|
|
|
|
|
|
function PlaceTerrainCircle(center, radius, color, step, offset, max_steps) |
|
step = step or guim |
|
offset = offset or guim |
|
local steps = Min(Max(12, (44 * radius) / (7 * step)), max_steps or 360) |
|
local last_pt |
|
local mapw, maph = terrain.GetMapSize() |
|
local vpstr = pstr("", 1024) |
|
for i = 0,steps do |
|
local x, y = RotateRadius(radius, MulDivRound(360*60, i, steps), center, true) |
|
x = Clamp(x, 0, mapw - height_tile) |
|
y = Clamp(y, 0, maph - height_tile) |
|
AppendVertex(vpstr, x, y, offset, color) |
|
end |
|
|
|
local line = PlaceObject("Polyline") |
|
line:SetMesh(vpstr) |
|
line:SetPos(center) |
|
line:AddMeshFlags(const.mfTerrainDistorted) |
|
return line |
|
end |
|
|
|
local function GetTerrainPointsPStr(vpstr, pt1, pt2, step, offset, color) |
|
step = step or guim |
|
offset = offset or guim |
|
local diff = pt2 - pt1 |
|
local steps = Max(2, 1 + diff:Len2D() / step) |
|
local mapw, maph = terrain.GetMapSize() |
|
vpstr = vpstr or pstr("", 1024) |
|
for i=1,steps do |
|
local pos = pt1 + MulDivRound(diff, i - 1, steps - 1) |
|
local x, y = pos:xy() |
|
x = Clamp(x, 0, mapw - height_tile) |
|
y = Clamp(y, 0, maph - height_tile) |
|
AppendVertex(vpstr, x, y, offset, color) |
|
end |
|
return vpstr |
|
end |
|
|
|
function PlaceTerrainLine(pt1, pt2, color, step, offset) |
|
local vpstr = GetTerrainPointsPStr(false, pt1, pt2, step, offset, color) |
|
local line = PlaceObject("Polyline") |
|
line:SetMesh(vpstr) |
|
line:SetPos((pt1 + pt2) / 2) |
|
line:AddMeshFlags(const.mfTerrainDistorted) |
|
return line |
|
end |
|
|
|
function PlaceTerrainBox(box, color, step, offset, mesh_obj, depth_test) |
|
local p = {box:ToPoints2D()} |
|
local m |
|
for i = 1, #p do |
|
m = GetTerrainPointsPStr(m, p[i], p[i + 1] or p[1], step, offset, color) |
|
end |
|
mesh_obj = mesh_obj or PlaceObject("Polyline") |
|
if depth_test ~= nil then |
|
mesh_obj:SetDepthTest(depth_test) |
|
end |
|
mesh_obj:SetMesh(m) |
|
mesh_obj:SetPos(box:Center()) |
|
mesh_obj:AddMeshFlags(const.mfTerrainDistorted) |
|
return mesh_obj |
|
end |
|
|
|
function PlaceTerrainPoly(p, color, step, offset, mesh_obj) |
|
local m |
|
local center = p[1] + ((p[1] - p[3]) / 2) |
|
for i = 1, #p do |
|
m = GetTerrainPointsPStr(m, p[i], p[i + 1] or p[1], step, offset, color) |
|
end |
|
mesh_obj = mesh_obj or PlaceObject("Polyline") |
|
mesh_obj:SetMesh(m) |
|
mesh_obj:SetPos(center) |
|
return mesh_obj |
|
end |
|
|
|
function PlacePolyLine(pts, clrs, depth_test) |
|
local line = PlaceObject("Polyline") |
|
line:SetEnumFlags(const.efVisible) |
|
if depth_test ~= nil then |
|
line:SetDepthTest(depth_test) |
|
end |
|
local vpstr = pstr("", 1024) |
|
local clr |
|
local pt0 |
|
for i, pt in ipairs(pts) do |
|
if IsValidPos(pt) then |
|
pt0 = pt0 or pt |
|
clr = type(clrs) == "table" and clrs[i] or clrs or clr |
|
AppendVertex(vpstr, pt, clr) |
|
end |
|
end |
|
line:SetMesh(vpstr) |
|
if pt0 then |
|
line:SetPos(pt0) |
|
end |
|
return line |
|
end |
|
|
|
function AppendSplineVertices(spline, color, step, min_steps, max_steps, vpstr) |
|
step = step or guim |
|
min_steps = min_steps or 7 |
|
max_steps = max_steps or 1024 |
|
local len = BS3_GetSplineLength3D(spline) |
|
local steps = Clamp(len / step, min_steps, max_steps) |
|
vpstr = vpstr or pstr("", (steps + 2) * const.pstrVertexSize) |
|
local x, y, z |
|
local x0, y0, z0 = BS3_GetSplinePos(spline, 0) |
|
AppendVertex(vpstr, x0, y0, z0, color) |
|
for i = 1,steps-1 do |
|
local x, y, z = BS3_GetSplinePos(spline, i, steps) |
|
AppendVertex(vpstr, x, y, z, color) |
|
end |
|
local x1, y1, z1 = BS3_GetSplinePos(spline, steps, steps) |
|
AppendVertex(vpstr, x1, y1, z1, color) |
|
return vpstr, point((x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2) |
|
end |
|
|
|
function PlaceSpline(spline, color, depth_test, step, min_steps, max_steps) |
|
local line = PlaceObject("Polyline") |
|
line:SetEnumFlags(const.efVisible) |
|
if depth_test ~= nil then |
|
line:SetDepthTest(depth_test) |
|
end |
|
local vpstr, pos = AppendSplineVertices(spline, color, step, min_steps, max_steps) |
|
line:SetMesh(vpstr) |
|
line:SetPos(pos) |
|
return line |
|
end |
|
|
|
function PlaceSplines(splines, color, depth_test, start_idx, step, min_steps, max_steps) |
|
local line = PlaceObject("Polyline") |
|
line:SetEnumFlags(const.efVisible) |
|
if depth_test ~= nil then |
|
line:SetDepthTest(depth_test) |
|
end |
|
local count = #(splines or "") |
|
local pos = point30 |
|
local vpstr = pstr("", count * 128 * const.pstrVertexSize) |
|
for i = (start_idx or 1), count do |
|
local _, posi = AppendSplineVertices(splines[i], color, step, min_steps, max_steps, vpstr) |
|
pos = pos + posi |
|
end |
|
if count > 0 then |
|
pos = pos / count |
|
end |
|
line:SetMesh(vpstr) |
|
line:SetPos(pos) |
|
return line |
|
end |
|
|
|
function PlaceBox(box, color, mesh_obj, depth_test) |
|
local p1, p2, p3, p4 = box:ToPoints2D() |
|
local minz, maxz = box:minz(), box:maxz() |
|
local vpstr = pstr("", 1024) |
|
if minz and maxz then |
|
if minz >= maxz - 1 then |
|
for _, p in ipairs{p1, p2, p3, p4, p1} do |
|
local x, y = p:xy() |
|
AppendVertex(vpstr, x, y, minz, color) |
|
end |
|
else |
|
for _, z in ipairs{minz, maxz} do |
|
for _, p in ipairs{p1, p2, p3, p4, p1} do |
|
local x, y = p:xy() |
|
AppendVertex(vpstr, x, y, z, color) |
|
end |
|
end |
|
AppendVertex(vpstr, p2:SetZ(maxz), color) |
|
AppendVertex(vpstr, p2:SetZ(minz), color) |
|
AppendVertex(vpstr, p3:SetZ(minz), color) |
|
AppendVertex(vpstr, p3:SetZ(maxz), color) |
|
AppendVertex(vpstr, p4:SetZ(maxz), color) |
|
AppendVertex(vpstr, p4:SetZ(minz), color) |
|
end |
|
else |
|
local z = terrain.GetHeight(p1) |
|
for _, p in ipairs{p2, p3, p4} do |
|
z = Max(z, terrain.GetHeight(p)) |
|
end |
|
for _, p in ipairs{p1, p2, p3, p4, p1} do |
|
local x, y = p:xy() |
|
AppendVertex(vpstr, x, y, z, color) |
|
end |
|
end |
|
mesh_obj = mesh_obj or PlaceObject("Polyline") |
|
if depth_test ~= nil then |
|
mesh_obj:SetDepthTest(depth_test) |
|
end |
|
mesh_obj:SetMesh(vpstr) |
|
mesh_obj:SetPos(box:Center()) |
|
return mesh_obj |
|
end |
|
|
|
function PlaceVector(pos, vec, color, depth_test) |
|
vec = vec or 10*guim |
|
vec = type(vec) == "number" and point(0, 0, vec) or vec |
|
return PlacePolyLine({pos, pos + vec}, color, depth_test) |
|
end |
|
|
|
function CreateTerrainCursorCircle(radius, color) |
|
color = color or RGB(23, 34, 122) |
|
radius = radius or 30 * guim |
|
|
|
local line = CreateCircleMesh(radius, color) |
|
line:SetPos(GetTerrainCursor()) |
|
line:SetMeshFlags(const.mfOffsetByTerrainCursor + const.mfTerrainDistorted + const.mfWorldSpace) |
|
return line |
|
end |
|
|
|
function CreateTerrainCursorSphere(radius, color) |
|
color = color or RGB(23, 34, 122) |
|
radius = radius or 30 * guim |
|
|
|
local line = PlaceObject("Mesh") |
|
line:SetMesh(CreateSphereVertices(radius, color)) |
|
line:SetShader(ProceduralMeshShaders.mesh_linelist) |
|
line:SetPos(GetTerrainCursor()) |
|
line:SetMeshFlags(const.mfOffsetByTerrainCursor + const.mfTerrainDistorted + const.mfWorldSpace) |
|
return line |
|
end |
|
|
|
function CreateOrientationMesh(pos) |
|
local o_mesh = Mesh:new() |
|
pos = pos or point(0, 0, 0) |
|
o_mesh:SetShader(ProceduralMeshShaders.mesh_linelist) |
|
local r = guim/4 |
|
local vpstr = pstr("", 1024) |
|
AppendVertex(vpstr, point(0, 0, 0), RGB(255, 0, 0)) |
|
AppendVertex(vpstr, point(r, 0, 0)) |
|
AppendVertex(vpstr, point(0, 0, 0), RGB(0, 255, 0)) |
|
AppendVertex(vpstr, point(0, r, 0)) |
|
AppendVertex(vpstr, point(0, 0, 0), RGB(0, 0, 255)) |
|
AppendVertex(vpstr, point(0, 0, r)) |
|
o_mesh:SetMesh(vpstr) |
|
o_mesh:SetPos(pos) |
|
return o_mesh |
|
end |
|
|
|
function CreateSphereMesh(radius, color, precision) |
|
local sphere_mesh = Mesh:new() |
|
sphere_mesh:SetMesh(CreateSphereVertices(radius, color)) |
|
sphere_mesh:SetShader(ProceduralMeshShaders.mesh_linelist) |
|
return sphere_mesh |
|
end |
|
|
|
function PlaceSphere(center, radius, color, depth_test) |
|
local sphere = CreateSphereMesh(radius, color) |
|
if depth_test ~= nil then |
|
sphere:SetDepthTest(depth_test) |
|
end |
|
sphere:SetPos(center) |
|
return sphere |
|
end |
|
|
|
function ShowMesh(time, func, ...) |
|
local ok, meshes = procall(func, ...) |
|
if not ok or not meshes then |
|
return |
|
end |
|
return CreateRealTimeThread(function(meshes, time) |
|
Msg("ShowMesh") |
|
WaitMsg("ShowMesh", time) |
|
if IsValid(meshes) then |
|
DoneObject(meshes) |
|
else |
|
DoneObjects(meshes) |
|
end |
|
end, meshes, time) |
|
end |
|
|
|
function CreateCircleMesh(radius, color, center) |
|
local circle_mesh = Mesh:new() |
|
circle_mesh:SetMesh(AppendCircleVertices(nil, center, radius, color, true)) |
|
circle_mesh:SetShader(ProceduralMeshShaders.default_polyline) |
|
return circle_mesh |
|
end |
|
|
|
function PlaceCircle(center, radius, color, depth_test) |
|
local circle = CreateCircleMesh(radius, color) |
|
if depth_test ~= nil then |
|
circle:SetDepthTest(depth_test) |
|
end |
|
circle:SetPos(center) |
|
return circle |
|
end |
|
|
|
function CreateConeMesh(center, displacement, radius1, radius2, axis, angle, color) |
|
local circle_mesh = Mesh:new() |
|
circle_mesh:SetMesh(AppendConeVertices(nil, center, displacement, radius1, radius2, axis, angle, color)) |
|
circle_mesh:SetShader(ProceduralMeshShaders.mesh_linelist) |
|
return circle_mesh |
|
end |
|
|
|
function CreateCylinderMesh(center, displacement, radius, axis, angle, color) |
|
local circle_mesh = Mesh:new() |
|
circle_mesh:SetMesh(AppendConeVertices(nil, center, displacement, radius, radius, axis, angle, color)) |
|
circle_mesh:SetShader(ProceduralMeshShaders.default_mesh) |
|
return circle_mesh |
|
end |
|
|
|
function CreateMoveGizmo() |
|
local g_MoveGizmo = MoveGizmo:new() |
|
CreateRealTimeThread(function() |
|
while true do |
|
g_MoveGizmo:OnMousePos(GetTerrainCursor()) |
|
Sleep(100) |
|
end |
|
end) |
|
end |
|
|
|
function CreateTerrainCursorTorus(radius1, radius2, axis, angle, color) |
|
color = color or RGB(255, 0, 0) |
|
radius1 = radius1 or 2.3 * guim |
|
radius2 = radius2 or 0.15 * guim |
|
axis = axis or axis_y |
|
angle = angle or 90 |
|
|
|
local line = PlaceObject("Mesh") |
|
local vpstr = pstr("", 1024) |
|
local normal = selo():GetPos() - camera.GetEye() |
|
local b = selo():GetPos() |
|
local bigTorusAxis, bigTorusAngle = GetAxisAngle(normal, axis_z) |
|
bigTorusAxis = Normalize(bigTorusAxis) |
|
bigTorusAngle = 180 - bigTorusAngle / 60 |
|
vpstr = AppendTorusVertices(vpstr, point(0, 0, 0), 2.3 * guim, 0.15 * guim, bigTorusAxis, bigTorusAngle, RGB(128, 128, 128)) |
|
vpstr = AppendTorusVertices(vpstr, point(0, 0, 0), 2.3 * guim, 0.15 * guim, axis_y, 90, RGB(255, 0, 0), normal, b) |
|
vpstr = AppendTorusVertices(vpstr, point(0, 0, 0), 2.3 * guim, 0.15 * guim, axis_x, 90, RGB(0, 255, 0), normal, b) |
|
vpstr = AppendTorusVertices(vpstr, point(0, 0, 0), 2.3 * guim, 0.15 * guim, axis_z, 0, RGB(0, 0, 255), normal, b) |
|
vpstr = AppendTorusVertices(vpstr, point(0, 0, 0), 3.5 * guim, 0.15 * guim, bigTorusAxis, bigTorusAngle, RGB(0, 192, 192)) |
|
line:SetMesh(vpstr) |
|
line:SetPos(selo():GetPos()) |
|
return line |
|
end |
|
|
|
function CreateObjSurfaceMesh(obj, surface_flag, color1, color2) |
|
if not IsValidPos(obj) then |
|
return |
|
end |
|
local v_pstr = pstr("", 1024) |
|
ForEachSurface(obj, surface_flag, function(pt1, pt2, pt3, v_pstr, color1, color2) |
|
local color |
|
if color1 and color2 then |
|
local rand = xxhash(pt1, pt2, pt3) % 1024 |
|
color = InterpolateRGB(color1, color2, rand, 1024) |
|
end |
|
v_pstr:AppendVertex(pt1, color) |
|
v_pstr:AppendVertex(pt2, color) |
|
v_pstr:AppendVertex(pt3, color) |
|
end, v_pstr, color1, color2) |
|
local mesh = PlaceObject("Mesh") |
|
mesh:SetMesh(v_pstr) |
|
mesh:SetPos(obj:GetPos()) |
|
mesh:SetMeshFlags(const.mfWorldSpace) |
|
mesh:SetDepthTest(true) |
|
if color1 and not color2 then |
|
mesh:SetColorModifier(color1) |
|
end |
|
return mesh |
|
end |
|
|
|
function FlatImageMesh(texture, width, height, glow_size, glow_period, glow_color) |
|
local text = PlaceObject("Mesh") |
|
local vpstr = pstr("", 1024) |
|
local color = RGB(255,255,255) |
|
local half_size_x = width or 1000 |
|
local half_size_y = height or 1000 |
|
glow_size = glow_size or 0 |
|
glow_period = glow_period or 0 |
|
glow_color = glow_color or RGB(255,255,255) |
|
|
|
AppendVertex(vpstr, point(-half_size_x, -half_size_y, 0), color, 0, 0) |
|
AppendVertex(vpstr, point(half_size_x, -half_size_y, 0), color, 1, 0) |
|
AppendVertex(vpstr, point(-half_size_x, half_size_y, 0), color, 0, 1) |
|
|
|
AppendVertex(vpstr, point(half_size_x, -half_size_y, 0), color, 1, 0) |
|
AppendVertex(vpstr, point(half_size_x, half_size_y, 0), color, 1, 1) |
|
AppendVertex(vpstr, point(-half_size_x, half_size_y, 0), color, 0, 1) |
|
|
|
text:SetMesh(vpstr) |
|
|
|
if texture then |
|
local use_sdf = false |
|
local padding = 0 |
|
local low_edge = 0 |
|
local high_edge = 0 |
|
if glow_size > 0 then |
|
use_sdf = true |
|
padding = 16 |
|
low_edge = 490 |
|
high_edge = 510 |
|
end |
|
text:SetTexture(0, ProceduralMeshBindResource("texture", texture, false, 0)) |
|
if glow_size > 0 then |
|
text:SetTexture(1, ProceduralMeshBindResource("texture", texture, true, 0, const.fmt_unorm16_c1)) |
|
text:SetShader(ProceduralMeshShaders.default_ui_sdf) |
|
else |
|
text:SetShader(ProceduralMeshShaders.default_ui) |
|
end |
|
local r, g, b = GetRGB(glow_color) |
|
text:SetUniforms(low_edge, high_edge, glow_size, glow_period, r, g, b) |
|
end |
|
|
|
return text |
|
end |
|
|
|
DefineClass.FlatTextMesh = { |
|
__parents = { "Mesh" }, |
|
properties = { |
|
{id = "font_id", editor = "number", read_only = true, default = 0, category = "Rasterize" }, |
|
{id = "text_style_id", editor = "preset_id", preset_class = "TextStyle", editor_preview = true, default = false, category = "Rasterize" }, |
|
{id = "text_scale", editor = "number", default = 1000, category = "Rasterize" }, |
|
{id = "text", editor = "text", default = "", category = "Rasterize" }, |
|
{id = "padding", editor = "number", default = 0, category = "Rasterize", help = "How much pixels to leave around the text(for effects)"}, |
|
|
|
{id = "width", editor = "number", default = 0, category = "Present", help = "In meters. Leave 0 to calculate automatically"}, |
|
{id = "height", editor = "number", default = 0, category = "Present", help = "In meters. Leave 0 to calculate automatically"}, |
|
{id = "text_color", editor = "color", default = RGB(255,255,255), category = "Present"}, |
|
{id = "effect_type", editor = "choice", items = {"none", "glow"}, default = "glow", category = "Present" }, |
|
{id = "effect_color", editor = "color", default = RGB(255,255,255), category = "Present"}, |
|
{id = "effect_size", editor = "number", default = 0, help = "In pixels from the rasterized image.", category = "Present" }, |
|
{id = "effect_period", editor = "number", default = 0, help = "1 pulse per each period seconds. ", category = "Present"}, |
|
} |
|
} |
|
|
|
function FlatTextMesh:Init() |
|
self:Recreate() |
|
end |
|
|
|
function FlatTextMesh:FetchEffectsFromTextStyle() |
|
local text_style = TextStyles[self.text_style_id] |
|
if not text_style then return end |
|
self.text_color = text_style.TextColor |
|
self.effect_type = text_style.ShadowType == "glow" and "glow" or "none" |
|
self.effect_color = text_style.ShadowColor |
|
self.effect_size = text_style.ShadowSize |
|
self.textstyle_id = self.text_style_id |
|
end |
|
|
|
function FlatTextMesh:SetColorFromTextStyle(text_style_id) |
|
self.text_style_id = text_style_id |
|
self.textstyle_id = text_style_id |
|
self:FetchEffectsFromTextStyle() |
|
self:Recreate() |
|
end |
|
|
|
function FlatTextMesh:CalculateSizes(max_width, max_height, default_scale) |
|
local width_pixels, height_pixels = UIL.MeasureText(self.text, self.font_id) |
|
local scale = 0 |
|
if max_width == 0 and max_height == 0 then |
|
scale = default_scale or 10000 |
|
elseif max_width == 0 then |
|
max_width = 1000000 |
|
elseif max_height == 0 then |
|
max_height = 1000000 |
|
end |
|
|
|
if scale == 0 then |
|
local scale1 = MulDivRound(max_width, 1000, width_pixels) |
|
local scale2 = MulDivRound(max_height, 1000, height_pixels) |
|
scale = Min(scale1, scale2) |
|
end |
|
|
|
self.width = MulDivRound(width_pixels, scale, 1000) |
|
self.height = MulDivRound(height_pixels, scale, 1000) |
|
end |
|
|
|
function FlatTextMesh:Recreate() |
|
local text_style = TextStyles[self.text_style_id] |
|
if not text_style then return end |
|
local font_id = text_style:GetFontIdHeightBaseline(self.text_scale) |
|
self.font_id = font_id |
|
|
|
local effect_type = self.effect_type |
|
local use_sdf = false |
|
local padding = 0 |
|
if effect_type == "glow" then |
|
use_sdf = true |
|
padding = 16 |
|
end |
|
|
|
local width_pixels, height_pixels = UIL.MeasureText(self.text, font_id) |
|
local width = self.width |
|
local height = self.height |
|
if width == 0 and height == 0 then |
|
local default_scale = 10000 |
|
width = MulDivRound(width_pixels, default_scale, 1000) |
|
height = MulDivRound(height_pixels, default_scale, 1000) |
|
end |
|
if width == 0 then |
|
width = MulDivRound(width_pixels, height, height_pixels) |
|
end |
|
if height == 0 then |
|
height = MulDivRound(height_pixels, width, width_pixels) |
|
end |
|
|
|
|
|
width = width + MulDivRound(width, padding * 2 * 1000, width_pixels * 1000) |
|
height = height + MulDivRound(height, padding * 2 * 1000, height_pixels * 1000) |
|
|
|
|
|
local vpstr = pstr("", 1024) |
|
local half_size_x = (width or 1000) / 2 |
|
local half_size_y = (height or 1000) / 2 |
|
local color = self.text_color |
|
AppendVertex(vpstr, point(-half_size_x, -half_size_y, 0), color, 0, 0) |
|
AppendVertex(vpstr, point(half_size_x, -half_size_y, 0), color, 1, 0) |
|
AppendVertex(vpstr, point(-half_size_x, half_size_y, 0), color, 0, 1) |
|
|
|
AppendVertex(vpstr, point(half_size_x, -half_size_y, 0), color, 1, 0) |
|
AppendVertex(vpstr, point(half_size_x, half_size_y, 0), color, 1, 1) |
|
AppendVertex(vpstr, point(-half_size_x, half_size_y, 0), color, 0, 1) |
|
|
|
self:SetMesh(vpstr) |
|
|
|
self:SetTexture(0, ProceduralMeshBindResource("text", self.text, font_id, use_sdf, padding)) |
|
local r, g, b = GetRGB(self.effect_color) |
|
self:SetUniforms(use_sdf and 1000 or 0, 0, self.effect_size * 1000, self.effect_period, r, g, b) |
|
self:SetShader(ProceduralMeshShaders.default_ui) |
|
end |
|
|
|
function TestUIRenderables() |
|
local pt = GetTerrainCursor() + point(0, 0, 100) |
|
for i = 0, 4 do |
|
local height = 700 |
|
local space = 5000 |
|
|
|
local text = FlatTextMesh:new({ |
|
text_style_id = "ProcMeshDefault", |
|
text_scale = 500 + 400 * i, |
|
text = "Hello world", |
|
height = height, |
|
}) |
|
text:SetPos(pt + point(i * space, 0, 0)) |
|
|
|
text = FlatTextMesh:new({ |
|
text_style_id = "ProcMeshDefaultFX", |
|
text_scale = 500 + 400 * i, |
|
text = "Hello world", |
|
height = height, |
|
effect_type = "glow", |
|
effect_size = 8, |
|
effect_period = 200, |
|
effect_color = RGB(255, 0, 0), |
|
}) |
|
text:SetPos(pt + point(i * space, 3000, 0)) |
|
text:SetGameFlags(const.gofRealTimeAnim) |
|
|
|
local mesh = FlatImageMesh("UI/MercsPortraits/Buns", 1000, 1000, 200 * i, 1000, RGB(255, 255, 255)) |
|
mesh:SetPos(pt + point(i * space, 6000, 0)) |
|
|
|
mesh = FlatImageMesh("UI/MercsPortraits/Buns", 1000, 1000) |
|
mesh:SetPos(pt + point(i * space, 9000, 0)) |
|
end |
|
end |
|
|
|
function DebugShowMeshes() |
|
local meshes = MapGet("map", "Mesh") |
|
OpenGedGameObjectEditor(meshes, true) |
|
end |
|
|
|
|
|
|
|
local function depth_test_values(obj) |
|
local tbl = {{value = "default", text = "default"}} |
|
local shader_id = obj.shader_id |
|
local shader_data = ProceduralMeshShaders[shader_id] |
|
if shader_data then |
|
if shader_data.depth_test == "runtime" or shader_data.depth_test == "never" then |
|
table.insert(tbl, {value = false, text = "never"}) |
|
end |
|
if shader_data.depth_test == "runtime" or shader_data.depth_test == "always" then |
|
table.insert(tbl, {value = true, text = "always"}) |
|
end |
|
end |
|
return tbl |
|
end |
|
DefineClass.CRMaterial = { |
|
__parents = {"PersistedRenderVars", "MeshParamSet"}, |
|
|
|
properties = { |
|
{ id = "ShaderName", editor = "choice", default = "default_mesh", items = function() return table.keys2(ProceduralMeshShaders, "sorted") end, read_only = true,}, |
|
{ id = "depth_test", editor = "choice", items = depth_test_values }, |
|
}, |
|
group = "CRMaterial", |
|
depth_test = "default", |
|
cloned_from = false, |
|
shader_id = "default_mesh", |
|
shader = false, |
|
pstr_buffer = false, |
|
dirty = false, |
|
} |
|
|
|
function CRMaterial:GetError() |
|
if not self.shader_id then |
|
return "CRMaterial without a shader_id." |
|
end |
|
if not ProceduralMeshShaders[self.shader_id] then |
|
return "ShaderID " .. self.shader_id .. " is not valid." |
|
end |
|
end |
|
|
|
function CRMaterial:GetShader() |
|
if self.shader then |
|
return self.shader |
|
end |
|
if self.shader_id then |
|
return ProceduralMeshShaders[self.shader_id] |
|
end |
|
return false |
|
end |
|
|
|
|
|
function CRMaterial:SetId(value) |
|
if self.cloned_from then |
|
self.id = value |
|
else |
|
PersistedRenderVars.SetId(self, value) |
|
end |
|
end |
|
|
|
function CRMaterial:SetGroup(value) |
|
if self.cloned_from then |
|
self.group = value |
|
else |
|
PersistedRenderVars.SetGroup(self, value) |
|
end |
|
end |
|
|
|
function CRMaterial:Register(...) |
|
if self.cloned_from then |
|
return |
|
end |
|
return PersistedRenderVars.Register(self, ...) |
|
end |
|
|
|
function CRMaterial:Clone() |
|
local obj = _G[self.class]:new({cloned_from = self.id}) |
|
obj:CopyProperties(self) |
|
return obj |
|
end |
|
|
|
function CRMaterial:GetDataPstr() |
|
if self.dirty or not self.pstr_buffer then |
|
self:Recreate() |
|
end |
|
return self.pstr_buffer |
|
end |
|
|
|
function CRMaterial:GetShaderName() |
|
return self.shader and self.shader.id or self.shader_id |
|
end |
|
|
|
function CRMaterial:Recreate() |
|
self.dirty = false |
|
self.pstr_buffer = self:WriteBuffer() |
|
end |
|
|
|
function CRMaterial:OnPreSave() |
|
self.pstr_buffer = nil |
|
end |
|
|
|
function CRMaterial:Apply() |
|
self:Recreate() |
|
if CurrentMap ~= "" then |
|
MapGet("map", "Mesh", function(o) |
|
local omtrl = o.CRMaterial |
|
if omtrl == self then |
|
o:SetCRMaterial(self) |
|
elseif (omtrl and omtrl.id == self.id) then |
|
for _, prop in ipairs(omtrl:GetProperties()) do |
|
local value = rawget(omtrl, prop.id) |
|
if value == nil or (not prop.read_only and not prop.no_edit) then |
|
omtrl:SetProperty(prop.id, self:GetProperty(prop.id)) |
|
end |
|
end |
|
omtrl:Recreate() |
|
o:SetCRMaterial(omtrl) |
|
end |
|
end) |
|
end |
|
end |
|
|
|
|
|
|
|
DefineClass.CRM_DebugMeshMaterial = { |
|
__parents = {"CRMaterial"}, |
|
|
|
shader_id = "debug_mesh", |
|
properties = { |
|
}, |
|
} |
|
|
|
|