204 lines
8.1 KiB
Nim
204 lines
8.1 KiB
Nim
# The contents of this file are subject to the Common Public Attribution License
|
|
# Version 1.0 (the “License”); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
# https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla
|
|
# Public License Version 1.1 but Sections 14 and 15 have been added to cover use
|
|
# of software over a computer network and provide for limited attribution for
|
|
# the Original Developer. In addition, Exhibit A has been modified to be
|
|
# consistent with Exhibit B.
|
|
#
|
|
# Software distributed under the License is distributed on an “AS IS” basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
# the specific language governing rights and limitations under the License.
|
|
#
|
|
# The Original Code is Myou Engine.
|
|
#
|
|
# the Original Developer is the Initial Developer.
|
|
#
|
|
# The Initial Developer of the Original Code is the Myou Engine developers.
|
|
# All portions of the code written by the Myou Engine developers are Copyright
|
|
# (c) 2024. All Rights Reserved.
|
|
#
|
|
# Alternatively, the contents of this file may be used under the terms of the
|
|
# GNU Affero General Public License version 3 (the [AGPL-3] License), in which
|
|
# case the provisions of [AGPL-3] License are applicable instead of those above.
|
|
#
|
|
# If you wish to allow use of your version of this file only under the terms of
|
|
# the [AGPL-3] License and not to allow others to use your version of this file
|
|
# under the CPAL, indicate your decision by deleting the provisions above and
|
|
# replace them with the notice and other provisions required by the [AGPL-3]
|
|
# License. If you do not delete the provisions above, a recipient may use your
|
|
# version of this file under either the CPAL or the [AGPL-3] License.
|
|
|
|
import ../types
|
|
import vmath except Quat, quat
|
|
import ../util
|
|
|
|
when defined(nimdoc):
|
|
type TYPES* = LightType | Light
|
|
|
|
# Forward declarations and ob type methods
|
|
proc newLight*(engine: MyouEngine, name: string="",
|
|
scene: Scene = nil,
|
|
light_type: LightType = PointLight,
|
|
color: Vec3 = vec3(1, 1, 1),
|
|
energy: float32 = 1,
|
|
cutoff_distance: float32 = 0,
|
|
diffuse_factor: float32 = 1,
|
|
specular_factor: float32 = 1,
|
|
light_radius: float32 = 0.01,
|
|
spot_size: float32 = 1.3,
|
|
spot_blend: float32 = 0.15,
|
|
use_shadow = false,
|
|
): Light
|
|
proc instance_physics*(this: Light)
|
|
|
|
method is_light*(self: GameObject): bool {.base.} =
|
|
## Return whether a GameObject is a Light
|
|
return false
|
|
method get_light*(self: GameObject): Light {.base.} =
|
|
## Get the Light object of a GameObject, or nil if it's not a light
|
|
return nil
|
|
method is_light*(this: Light): bool =
|
|
## Return whether a GameObject is a Light
|
|
return true
|
|
method get_light*(this: Light): Light =
|
|
## Get the Light object of a GameObject, or nil if it's not a light
|
|
return this
|
|
# End forward declarations and ob type methods
|
|
|
|
import ./gameobject
|
|
import ./mesh
|
|
import ../scene
|
|
import ../shadows/shadow_common
|
|
import ../shadows/simple_shadow
|
|
import quickhull
|
|
|
|
proc newLight*(engine: MyouEngine, name: string="",
|
|
scene: Scene = nil,
|
|
light_type: LightType = PointLight,
|
|
color: Vec3 = vec3(1, 1, 1),
|
|
energy: float32 = 1,
|
|
cutoff_distance: float32 = 0,
|
|
diffuse_factor: float32 = 1,
|
|
specular_factor: float32 = 1,
|
|
light_radius: float32 = 0.01,
|
|
spot_size: float32 = 1.3,
|
|
spot_blend: float32 = 0.15,
|
|
use_shadow = false,
|
|
): Light =
|
|
## Create a new Light object. If you supply `scene` it will be added to that
|
|
## scene.
|
|
var this = new Light
|
|
discard procCall(this.GameObject.initGameObject(engine, name))
|
|
this.otype = TLight
|
|
this.light_type = light_type
|
|
this.color = color
|
|
this.energy = energy
|
|
this.light_radius = light_radius
|
|
this.spot_size = spot_size
|
|
this.spot_blend = spot_blend
|
|
this.cutoff_distance = cutoff_distance
|
|
this.diffuse_factor = diffuse_factor
|
|
this.specular_factor = specular_factor
|
|
this.use_shadow = use_shadow
|
|
if scene != nil:
|
|
scene.add_object(this, name=name)
|
|
return this
|
|
|
|
when defined(myouDebugShadows):
|
|
var debug_mesh: Mesh
|
|
|
|
proc configure_shadow*(self: Light,
|
|
camera: Camera = nil,
|
|
max_distance: float32 = 0.0,
|
|
objects = self.scene.children) =
|
|
## Configure a shadow for this light object, either by following a camera
|
|
## passed as argument, or a static shadow for the specified objects.
|
|
##
|
|
## `max_distance` is only used when `camera` is supplied.
|
|
##
|
|
## `objects` is only used in static mode (when `camera` is nil). In this
|
|
## mode, the shadow map will be tailored for the size of the bounding boxes
|
|
## of the objects. It will exclude flat objects (planes) that are under every
|
|
## other object because they can't project a shadow to themselves.
|
|
for s in self.shadows:
|
|
s.destroy()
|
|
self.shadows.setLen 0
|
|
|
|
if camera == nil:
|
|
# Static shadow strategy (ideal for small scenes):
|
|
# For casting shadows, include bounds for all visible meshes
|
|
# except for ground planes.
|
|
|
|
# TODO: when a ground plane is detected, if light Z is positive
|
|
# (pointing upwards) the shadow map should just be cleared with a depth
|
|
# of -1.0 (all in shadow); or render it with some offset, or allow the
|
|
# detection of ground planes with some amount of thickness to prevent
|
|
# light bleeding.
|
|
|
|
# TODO: instead of a ground plane, we can just exclude the largest
|
|
# convex object, as long as we can clamp the polygons outside the
|
|
# frustum. Can we avoid clipping by setting W?
|
|
|
|
self.scene.update_all_matrices()
|
|
var casters: seq[Vec3]
|
|
for ob in objects:
|
|
if not (ob.is_mesh and ob.visible):
|
|
continue
|
|
let me = ob.get_mesh
|
|
let bb = me.bound_box
|
|
if bb[0] == bb[1]:
|
|
continue
|
|
let world_dim = (ob.world_matrix * vec4(bb[1] - bb[0], 0.0)).xyz
|
|
var casts = true
|
|
if world_dim.z < 0.00001:
|
|
# Possible flat floor plane,
|
|
# check if all meshes have their centers above it
|
|
when defined(myouDebugShadows):
|
|
echo "Floor plane detected: ", ob.name
|
|
var all_above = true
|
|
let z = ob.world_center.z - 0.00001
|
|
for ob2 in self.scene.children:
|
|
if ob2.is_mesh and ob2.visible and ob2.world_center.z < z:
|
|
when defined(myouDebugShadows):
|
|
echo " Object is below floor plane: ", ob2.name
|
|
all_above = false
|
|
break
|
|
casts = not all_above
|
|
if casts:
|
|
for v in box_corners(bb):
|
|
casters.add ob.world_matrix * v
|
|
|
|
if casters.len == 0:
|
|
return
|
|
when defined(myouDebugShadows):
|
|
if debug_mesh.nonNil:
|
|
debug_mesh.destroy()
|
|
debug_mesh = self.engine.newMesh(vertex_count = 100, draw_method = Lines)
|
|
debug_mesh.materials.add self.engine.newSolidMaterial("green", vec4(0,1,0,1))
|
|
debug_mesh.visible = true
|
|
self.scene.add_object debug_mesh
|
|
|
|
let hull = quickhull(casters)
|
|
for f in hull:
|
|
debug_mesh.add_vertex f.points[0], vec4(1)
|
|
for v in f.points[1..^1]:
|
|
debug_mesh.add_vertex v, vec4(1)
|
|
debug_mesh.add_vertex v, vec4(1)
|
|
debug_mesh.add_vertex f.points[0], vec4(1)
|
|
debug_mesh.data.update_varray()
|
|
|
|
casters = quickhull_points(casters)
|
|
let shadow = newSimpleShadowManager(self, use_camera = false)
|
|
shadow.caster_bounding_points = casters
|
|
else:
|
|
# TODO: Use a sphere as bounds.
|
|
# TODO: Have a mode where the sphere surrounds all possible orientations
|
|
# of the camera and some amount of translation.
|
|
newSimpleShadowManager(self, use_camera = true, far = max_distance)
|
|
|
|
|
|
proc instance_physics*(this: Light) =
|
|
discard
|
|
|