Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OoT] Occlusion planes system #360

Merged
merged 20 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions fast64_internal/f3d/f3d_material.py
Original file line number Diff line number Diff line change
Expand Up @@ -4510,6 +4510,7 @@ def mat_register():
default=10,
)
Object.f3d_lod_always_render_farthest = bpy.props.BoolProperty(name="Always Render Farthest LOD")
Object.is_occlusion_planes = bpy.props.BoolProperty(name="Is Occlusion Planes")

VIEW3D_HT_header.append(draw_f3d_render_settings)

Expand All @@ -4530,6 +4531,7 @@ def mat_unregister():
del Scene.f3dUserPresetsOnly
del Object.f3d_lod_z
del Object.f3d_lod_always_render_farthest
del Object.is_occlusion_planes

for cls in reversed(mat_classes):
unregister_class(cls)
Expand Down
2 changes: 2 additions & 0 deletions fast64_internal/f3d/occlusion_planes/exporter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .classes import OcclusionPlaneCandidate, OcclusionPlaneCandidatesList
from .functions import addOcclusionQuads
18 changes: 18 additions & 0 deletions fast64_internal/f3d/occlusion_planes/exporter/classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from dataclasses import dataclass
from mathutils import Vector
from typing import List


@dataclass
class OcclusionPlaneCandidate:
v0: Vector
v1: Vector
v2: Vector
v3: Vector
weight: float


class OcclusionPlaneCandidatesList:
def __init__(self, ownerName):
self.planes: List[OcclusionPlaneCandidate] = []
self.name: str = ownerName + "_occlusionPlaneCandidates"
67 changes: 67 additions & 0 deletions fast64_internal/f3d/occlusion_planes/exporter/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import bpy
from mathutils import Vector, Matrix

from ....utility import PluginError
from .classes import OcclusionPlaneCandidate, OcclusionPlaneCandidatesList
from ...f3d_writer import getColorLayer


def addOcclusionQuads(
obj: bpy.types.Object,
candidatesList: OcclusionPlaneCandidatesList,
includeChildren: bool,
transformRelToScene: Matrix,
):
if obj.type == "MESH" and obj.is_occlusion_planes:
mesh = obj.data
color_layer = getColorLayer(mesh, layer="Col")
if not color_layer:
raise PluginError(
f'Occlusion planes mesh {obj.name} must have a vertex colors layer named "Col", which you paint the weight for each plane into.'
)
for polygon in mesh.polygons:
# Weight is the average of R, G, and B across the four corners
totalColor = Vector((0.0, 0.0, 0.0))
verts = []
if polygon.loop_total != 4:
raise PluginError(
f"Occlusion planes mesh {obj.name} contains a polygon with {polygon.loop_total} verts. Occlusion planes must be quads."
)
for loopIndex in polygon.loop_indices:
loop = mesh.loops[loopIndex]
color = color_layer[loop.index].color
totalColor += Vector((color[0], color[1], color[2]))
verts.append(transformRelToScene @ obj.matrix_world @ mesh.vertices[loop.vertex_index].co)
totalColor *= 0.25
weight = (totalColor[0] + totalColor[1] + totalColor[2]) / 3.0
# Check that the quad is planar. Are the normals to the two tris forming
# halves of the quad pointing in the same direction? If either tri is
# degenerate, it's OK.
edge1 = (verts[1] - verts[0]).normalized()
midA = (verts[2] - verts[0]).normalized()
edge3 = (verts[3] - verts[0]).normalized()
normal1 = edge1.cross(midA)
normal2 = midA.cross(edge3)
if (
normal1.length > 0.001
and normal2.length > 0.001
and normal1.normalized().dot(normal2.normalized()) < 0.999
):
raise PluginError(f"Occlusion planes mesh {obj.name} contains a quad which is not planar (flat).")
# Check that the quad is convex. Are the cross products at each corner
# all pointing in the same direction?
edge01 = verts[1] - verts[0]
edge12 = verts[2] - verts[1]
edge23 = verts[3] - verts[2]
edge30 = verts[0] - verts[3]
cross1 = edge01.cross(edge12)
cross2 = edge12.cross(edge23)
cross3 = edge23.cross(edge30)
cross0 = edge30.cross(edge01)
if cross0.dot(cross1) < 0.0 or cross0.dot(cross2) < 0.0 or cross0.dot(cross3) < 0.0:
raise PluginError(f"Occlusion planes mesh {obj.name} contains a quad which is not convex.")
candidatesList.planes.append(OcclusionPlaneCandidate(verts[0], verts[1], verts[2], verts[3], weight))

if includeChildren:
for child in obj.children:
addOcclusionQuads(child, candidatesList, includeChildren, transformRelToScene)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .occlusion_planes import occCandidatesListToC
Yanis42 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from mathutils import Vector

from ...exporter import OcclusionPlaneCandidate, OcclusionPlaneCandidatesList
from .....utility import CData


def occVertexToC(vertex: Vector):
return "\t\t\t{" + ", ".join([str(int(round(a))) for a in vertex]) + "},\n"
Yanis42 marked this conversation as resolved.
Show resolved Hide resolved


def occCandidateToC(candidate: OcclusionPlaneCandidate):
return (
"\t{\n\t\t{\n"
+ "".join(map(occVertexToC, [candidate.v0, candidate.v1, candidate.v2, candidate.v3]))
+ "\t\t},\n\t\t"
+ str(candidate.weight)
+ "f\n\t},\n"
)


def occCandidatesListToC(candidatesList: OcclusionPlaneCandidatesList):
cdata = CData()
if len(candidatesList.planes) > 0:
name = "OcclusionPlaneCandidate " + candidatesList.name + "[" + str(len(candidatesList.planes)) + "]"
cdata.header = "extern " + name + ";\n"
cdata.source = (
name + " = {\n" + "".join(occCandidateToC(candidate) for candidate in candidatesList.planes) + "};\n\n"
)
return cdata
5 changes: 5 additions & 0 deletions fast64_internal/oot/f3d/panels.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import bpy
from bpy.types import Panel, Mesh, Armature
from bpy.utils import register_class, unregister_class
from ...panels import OOT_Panel
Expand Down Expand Up @@ -33,6 +34,10 @@ def draw(self, context):
# prop_split(box, obj, "ootDrawLayer", "Draw Layer")
box.prop(obj, "ignore_render")
box.prop(obj, "ignore_collision")
if bpy.context.scene.f3d_type == "F3DEX3":
box.prop(obj, "is_occlusion_planes")
if obj.is_occlusion_planes and (not obj.ignore_render or not obj.ignore_collision):
box.label(icon="INFO", text="Suggest Ignore Render & Ignore Collision.")

if not (obj.parent is not None and isinstance(obj.parent.data, Armature)):
actorScaleBox = box.box().column()
Expand Down
2 changes: 2 additions & 0 deletions fast64_internal/oot/oot_level_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
GfxListTag,
GfxList,
)
from ..f3d.occlusion_planes.exporter import OcclusionPlaneCandidatesList


class OOTCommonCommands:
Expand Down Expand Up @@ -375,6 +376,7 @@ def __init__(self, index, name, model, roomShape):
self.ownerName = toAlnum(name)
self.index = index
self.actorList = set()
self.occlusion_planes = OcclusionPlaneCandidatesList(self.roomName())
self.mesh = OOTRoomMesh(self.roomName(), roomShape, model)

# Room behaviour
Expand Down
15 changes: 15 additions & 0 deletions fast64_internal/oot/oot_level_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .oot_object import addMissingObjectsToAllRoomHeaders
from .oot_f3d_writer import writeTextureArraysNew, writeTextureArraysExisting1D
from .collision.constants import decomp_compat_map_CameraSType
from ..f3d.occlusion_planes.exporter import addOcclusionQuads

from .collision.exporter import (
OOTCameraData,
Expand Down Expand Up @@ -90,6 +91,8 @@ def ootCreateSceneHeader(levelC):
sceneHeader.append(levelC.sceneCutscenesC[i])
for roomName, roomMainC in levelC.roomMainC.items():
sceneHeader.append(roomMainC)
for roomName, roomOcclusionPlanesC in levelC.roomOcclusionPlanesC.items():
sceneHeader.append(roomOcclusionPlanesC)
for roomName, roomShapeInfoC in levelC.roomShapeInfoC.items():
sceneHeader.append(roomShapeInfoC)
for roomName, roomModelC in levelC.roomModelC.items():
Expand Down Expand Up @@ -149,6 +152,7 @@ def ootExportSceneToC(originalSceneObj, transformMatrix, sceneName, DLFormat, sa
for i in range(len(scene.rooms)):
roomC = CData()
roomC.append(levelC.roomMainC[scene.rooms[i].roomName()])
roomC.append(levelC.roomOcclusionPlanesC[scene.rooms[i].roomName()])
roomC.append(levelC.roomShapeInfoC[scene.rooms[i].roomName()])
roomC.append(levelC.roomModelC[scene.rooms[i].roomName()])
writeCDataSourceOnly(
Expand Down Expand Up @@ -180,6 +184,11 @@ def ootExportSceneToC(originalSceneObj, transformMatrix, sceneName, DLFormat, sa
writeCDataSourceOnly(
ootPreprendSceneIncludes(scene, roomMainC), os.path.join(levelPath, roomName + "_main.c")
)
for roomName, roomOcclusionPlanesC in levelC.roomOcclusionPlanesC.items():
if len(roomOcclusionPlanesC.source) > 0:
writeCDataSourceOnly(
ootPreprendSceneIncludes(scene, roomOcclusionPlanesC), os.path.join(levelPath, roomName + "_occ.c")
)
for roomName, roomShapeInfoC in levelC.roomShapeInfoC.items():
writeCDataSourceOnly(
ootPreprendSceneIncludes(scene, roomShapeInfoC), os.path.join(levelPath, roomName + "_model_info.c")
Expand Down Expand Up @@ -237,6 +246,7 @@ def writeOtherSceneProperties(scene, exportInfo, levelC):
levelC.sceneCutscenesIsUsed(),
len(scene.rooms),
len(levelC.sceneCutscenesC),
[len(occ.source) > 0 for occ in levelC.roomOcclusionPlanesC.values()],
)
modifySceneFiles(scene, exportInfo)

Expand Down Expand Up @@ -555,6 +565,11 @@ def ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, conv
cullGroup.position = centroid
cullGroup.cullDepth = radius

if bpy.context.scene.f3d_type == "F3DEX3":
addOcclusionQuads(
obj, room.occlusion_planes, True, transformMatrix @ sceneObj.matrix_world.inverted()
)

room.mesh.terminateDLs()
room.mesh.removeUnusedEntries()
ootProcessEmpties(scene, room, sceneObj, roomObj, transformMatrix)
Expand Down
33 changes: 26 additions & 7 deletions fast64_internal/oot/scene/exporter/to_c/room_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,59 @@ def getWindSettingsCmd(outRoom: OOTRoom):
)


def getOcclusionPlaneCandidatesListCmd(outRoom: OOTRoom):
return (
indent
+ f"SCENE_CMD_OCCLUSION_PLANE_CANDIDATES_LIST({len(outRoom.occlusion_planes.planes)}, {outRoom.occlusion_planes.name})"
)


def getRoomShapeCmd(outRoom: OOTRoom):
return indent + f"SCENE_CMD_ROOM_SHAPE(&{outRoom.mesh.headerName()})"


def getObjectListCmd(outRoom: OOTRoom, headerIndex: int):
return (
indent + "SCENE_CMD_OBJECT_LIST("
) + f"{outRoom.getObjectLengthDefineName(headerIndex)}, {outRoom.objectListName(headerIndex)}),\n"
) + f"{outRoom.getObjectLengthDefineName(headerIndex)}, {outRoom.objectListName(headerIndex)})"


def getActorListCmd(outRoom: OOTRoom, headerIndex: int):
return (
indent + "SCENE_CMD_ACTOR_LIST("
) + f"{outRoom.getActorLengthDefineName(headerIndex)}, {outRoom.actorListName(headerIndex)}),\n"
) + f"{outRoom.getActorLengthDefineName(headerIndex)}, {outRoom.actorListName(headerIndex)})"


def getRoomCommandList(outRoom: OOTRoom, headerIndex: int):
cmdListData = CData()
declarationBase = f"SceneCmd {outRoom.roomName()}_header{headerIndex:02}"

getCmdFuncList = [
getCmdFunc1ArgList = [
getEchoSettingsCmd,
getRoomBehaviourCmd,
getSkyboxDisablesCmd,
getTimeSettingsCmd,
getRoomShapeCmd,
]

getCmdFunc2ArgList = []

if outRoom.setWind:
getCmdFunc1ArgList.append(getWindSettingsCmd)

if len(outRoom.occlusion_planes.planes) > 0:
getCmdFunc1ArgList.append(getOcclusionPlaneCandidatesListCmd)

if len(outRoom.objectIDList) > 0:
getCmdFunc2ArgList.append(getObjectListCmd)

if len(outRoom.actorList) > 0:
getCmdFunc2ArgList.append(getActorListCmd)

roomCmdData = (
(outRoom.getAltHeaderListCmd(outRoom.alternateHeadersName()) if outRoom.hasAlternateHeaders() else "")
+ (",\n".join(getCmd(outRoom) for getCmd in getCmdFuncList) + ",\n")
+ (getWindSettingsCmd(outRoom) if outRoom.setWind else "")
+ (getObjectListCmd(outRoom, headerIndex) if len(outRoom.objectIDList) > 0 else "")
+ (getActorListCmd(outRoom, headerIndex) if len(outRoom.actorList) > 0 else "")
+ "".join(getCmd(outRoom) + ",\n" for getCmd in getCmdFunc1ArgList)
+ "".join(getCmd(outRoom, headerIndex) + ",\n" for getCmd in getCmdFunc2ArgList)
+ outRoom.getEndCmd()
)

Expand Down
3 changes: 3 additions & 0 deletions fast64_internal/oot/scene/exporter/to_c/scene.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .....utility import CData, PluginError
from .....f3d.f3d_gbi import TextureExportSettings
from .....f3d.occlusion_planes.exporter.to_c import occCandidatesListToC
from ....oot_level_classes import OOTScene
from .scene_header import getSceneData, getSceneModel
from .scene_collision import getSceneCollision
Expand Down Expand Up @@ -27,6 +28,7 @@ def __init__(self):

# Files for room segments
self.roomMainC = {}
self.roomOcclusionPlanesC = {}
self.roomShapeInfoC = {}
self.roomModelC = {}

Expand All @@ -50,6 +52,7 @@ def getSceneC(outScene: OOTScene, textureExportSettings: TextureExportSettings):
raise PluginError(f"Error: Room {outRoom.index} has no mesh children.")

sceneC.roomMainC[outRoomName] = getRoomData(outRoom)
sceneC.roomOcclusionPlanesC[outRoomName] = occCandidatesListToC(outRoom.occlusion_planes)
sceneC.roomShapeInfoC[outRoomName] = roomShapeInfo
sceneC.roomModelC[outRoomName] = roomModel

Expand Down
4 changes: 2 additions & 2 deletions fast64_internal/oot/scene/exporter/to_c/scene_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ def getSceneCommandList(outScene: OOTScene, headerIndex: int):

sceneCmdData = (
(outScene.getAltHeaderListCmd(outScene.alternateHeadersName()) if outScene.hasAlternateHeaders() else "")
+ (",\n".join(getCmd(outScene) for getCmd in getCmdFunc1ArgList) + ",\n")
+ (",\n".join(getCmd(outScene, headerIndex) for getCmd in getCmdFunc2ArgList) + ",\n")
+ "".join(getCmd(outScene) + ",\n" for getCmd in getCmdFunc1ArgList)
+ "".join(getCmd(outScene, headerIndex) + ",\n" for getCmd in getCmdFunc2ArgList)
+ outScene.getEndCmd()
)

Expand Down
12 changes: 10 additions & 2 deletions fast64_internal/oot/scene/exporter/to_c/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import enum

from dataclasses import dataclass, field
from typing import Optional
from typing import List, Optional
from .....utility import PluginError, writeFile, indent
from ....oot_utility import ExportInfo, getSceneDirFromLevelName

Expand Down Expand Up @@ -203,7 +203,13 @@ def to_c(self):


def editSpecFile(
isScene: bool, exportInfo: ExportInfo, hasSceneTex: bool, hasSceneCS: bool, roomTotal: int, csTotal: int
isScene: bool,
exportInfo: ExportInfo,
hasSceneTex: bool,
hasSceneCS: bool,
roomTotal: int,
csTotal: int,
roomIndexHasOcclusion: List[bool],
):
global buildDirectory

Expand Down Expand Up @@ -283,6 +289,8 @@ def editSpecFile(
SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_model.o"'),
]
)
if roomIndexHasOcclusion[i]:
roomCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_occ.o"'))

roomCmds.append(SpecEntryCommand(CommandType.NUMBER, "3"))
specFile.append(SpecEntry(None, roomCmds))
Expand Down
2 changes: 1 addition & 1 deletion fast64_internal/oot/scene/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

def ootRemoveSceneC(exportInfo):
modifySceneTable(None, exportInfo)
editSpecFile(False, exportInfo, False, False, 0, 0)
editSpecFile(False, exportInfo, False, False, 0, 0, [])
deleteSceneFiles(exportInfo)


Expand Down
4 changes: 4 additions & 0 deletions fast64_internal/sm64/sm64_geolayout_bone.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ def draw(self, context):
prop_split(col, geo_asm, "param", "Parameter")
col.prop(obj, "ignore_render")
col.prop(obj, "ignore_collision")
if bpy.context.scene.f3d_type == "F3DEX3":
box.prop(obj, "is_occlusion_planes")
if obj.is_occlusion_planes and (not obj.ignore_render or not obj.ignore_collision):
box.label(icon="INFO", text="Suggest Ignore Render & Ignore Collision.")
col.prop(obj, "use_f3d_culling")
if context.scene.exportInlineF3D:
col.prop(obj, "bleed_independently")
Expand Down
Loading